Useful tools… to find NA in a column of a data frame: new_DF <- DF[is.na(DF$Var),] eg. new_lemma_new <- lemma_new[is.na(lemma_new$word),]

library(styler)

#### Functions #####

#--------------------
readpercent <- function()
{ 
  n <- readline(prompt="Enter a percent, positive number selects head & negative selects tail[-100-100]: ")
  if(!(grepl("^-?[0-9]+$",n)) || as.integer(n) > 100 || as.integer(n) < -100) #
  {
    return(readpercent())
  }
  return(as.integer(n))
}

#--------------------
HistogramFun<-function(funding, fstatedPercent, numRows) {
  cat("typeof funding",typeof(funding), "typeof nrows", typeof(numRows), "nrows funding ", typeof(numRows), "numRows::", numRows, "\n")
  if (fstatedPercent < 0) {
    color1 <- "gray"; 
    color2 <- "gray40";
    nInHead <- as.integer(((100 + fstatedPercent)/100) * numRows)
    nInTail <- numRows - nInHead
    headPercent <- fstatedPercent + 100
    tailPercent <- abs(fstatedPercent)
  } else {
    color2 <- "gray"
    color1 <- "gray40"
    nInHead <- as.integer((fstatedPercent/100) * numRows)
    nInTail <- numRows - nInHead
    headPercent <- fstatedPercent
    tailPercent <- 100 - fstatedPercent
  }
  cols = c(rep(color1, times = nInHead), rep(color2, times = nInTail))
  #ylim= c(0, 3),  
  barplot(funding, col = cols, border=cols, main = NULL, 
          xpd = FALSE, las=1
  )
  
  #legend("right", legend = c("Head (10%)", "Long Tail (90%)"), fill = c("gray40", "gray"), cex=.9)
  legend("right", 
         legend = c(paste("Head (", headPercent, "%)", sep = ""), 
                    paste("Tail (", tailPercent, "%)", sep = "")), 
         fill = c(color1, color2), cex=.9)
  title("The focus for later analysis is shaded dark", cex.main=.95, line= -2) 
  title(xlab="Size Ordered Grants - bar for each grant", line=0)
  title(ylab="Dollar Value of Award in Millions", line = 2) 
}
# Read in the file
MyData <- read.csv("data/CNS.csv", header=TRUE, encoding = "UTF-8", stringsAsFactors=FALSE) # tab delimited

# pull off the directory part of the file name to use later for saving output files.
#topicDir <- tools::file_path_sans_ext(filename)
topicDir <- "Awards_CNS"

dir.create(topicDir, showWarnings = FALSE)



# decide on which parts of the curve to analyze
# User must enter the # on the console!
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
nsf_funding <- data_frame(id = as.character(MyData$AwardNumber),
                          abstract = MyData$Abstract,
                          amount = MyData$AwardedAmountToDate)
Warning: `data_frame()` was deprecated in tibble 1.1.0.
Please use `tibble()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
# remove html tags in the form <anything>
# ??? drop records with empty abstracts
# changed PBH 2019-02-27 to put in a space when html is replaced
nsf_funding$abstract <- gsub("<[^>]+>", " ", nsf_funding$abstract)
# remove where the word is a number
nsf_funding$abstract <- gsub("\\d", "", nsf_funding$abstract)
# if there are any "words" with imbedded periods Eg "John.P.Doud, replace the peroid with a space.
nsf_funding$abstract <- gsub("\\.", " ", nsf_funding$abstract)
# remove "'s"
nsf_funding$abstract <- gsub("'s", "", nsf_funding$abstract) # to fix eg trichoptera:hydropsychidae 
nsf_funding$abstract <- gsub(":", " ", nsf_funding$abstract)
#nsf_funding$abstract <- nsf_funding[-grep('^\\d+$', nsf_funding$abstract),]
# Convert the string repreentation of numbers in the amount field by removing "$" and ","
nsf_funding$amount <- as.numeric(gsub('[$,]', '', nsf_funding$amount))

# sort the abstracts by the amount awarded
#abstract_sort <- nsf_funding %>% 
nsf_funding <- nsf_funding[order(-nsf_funding$amount), ]

statedPercent <- 100
# if statedPercent is not set in the code ask the user
if(!exists("statedPercent")){statedPercent <- readpercent();}
# if it is negative then it is really the tail and the head is the length the array minus that amount

#This displays on the screen
HistogramFun(nsf_funding$amount/1000000, statedPercent, nrow(nsf_funding))
typeof funding double typeof nrows integer nrows funding  integer numRows:: 3000 
#This saves a file
png(paste(topicDir,"/focusHistogram.png", sep=""), width = 5, height = 4, units = 'in', res = 300)
HistogramFun(nsf_funding$amount/1000000, statedPercent, nrow(nsf_funding))
typeof funding double typeof nrows integer nrows funding  integer numRows:: 3000 
dev.off()
quartz_off_screen 
                2 

if (statedPercent != 100) {
  # Only do this if you want to only analyse part of the collection
  nsf_funding <- head(nsf_funding, as.integer(nrow(nsf_funding)*(headpercent/100)))
}

For astronomy data may need to hide the few big projects to see the curve.

### Only run this to use custom stop words. ####
library(tidytext)
#Custom stop words
#adapt for Bio!!!
my_stop_words <- bind_rows(stop_words, 
                           data_frame(word = c("formation", "science", "stars", "galaxies",
                                               "data", "star",
                                               "project", "astronomy", "research", "students", "galaxy",
                                               "program", "public", "observations", "study",
                                               "graduate", "results", "team", "award",
                                               "dr", as.character(1:12)),
                                                
                                      lexicon = rep("custom", 32)))
library(tidyverse) # R is better when it is tidy
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.3     ✓ purrr   0.3.4
✓ tibble  3.0.4     ✓ stringr 1.4.0
✓ tidyr   1.1.2     ✓ forcats 0.5.0
✓ readr   1.4.0     
── Conflicts ────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(stringr)  # for string manipulation
# Save copy of abstracts for bigram work
library(tidytext)
nsf_funding_bigram <- nsf_funding
nsf_funding_save <- nsf_funding # This will be used to find bigrams in the documents.

nsf_funding <- nsf_funding %>% 
  unnest_tokens(word, abstract)  %>% 
  #anti_join(my_stop_words) # Add custom stop words.
  anti_join(stop_words) # Use normal stop words.
Joining, by = "word"
# remove rows that are null.
#nsf_funding <- nsf_funding[!(nsf_funding$word == null), ]
nsf_funding <- nsf_funding[!is.null(nsf_funding$word) ]
nsf_funding %>% filter(nsf_funding$word != "null" )
nsf_funding_bigram <- nsf_funding_bigram %>% 
  unnest_tokens(bigram, abstract, token = "ngrams", n = 2) %>% 
  #anti_join(my_stop_words) # Add custom stop words.
  anti_join(stop_words, by = c("bigram" = "word")) # Use normal stop words.
lemma_unique <- nsf_funding %>%
  select(word) %>%
  mutate(word_clean = str_replace_all(word,"\u2019s|'s","")) %>%
#  mutate(word_clean = ifelse(str_detect(word_clean,"[^[:alpha:]]"),NA,word_clean)) %>%
  # this :alpha: causes problems with UTF-8
 # mutate(word_clean = ifelse(str_detect(word_clean,"[^[:alpha:]]"),NA,word_clean)) %>%
  filter(!duplicated(word_clean)) %>%
  filter(!is.na(word_clean)) %>%
  arrange(word)
#load packages koRupus and sylly
library(dplyr)
library(sylly)
library(koRpus)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
For information on available language packages for 'koRpus', run

  available.koRpus.lang()

and see ?install.koRpus.lang()


Attaching package: ‘koRpus’

The following object is masked from ‘package:readr’:

    tokenize
#install.koRpus.lang("en")
library(koRpus.lang.en)
# setup for lemmanization
# Outside of R Install tree-tagger from https://cran.r-project.org/web/packages/koRpus/vignettes/koRpus_vignette.html
# http://www.cis.uni-muenchen.de/~schmid/tools/TreeTagger/#parfiles
# to install tree-tagger in order to run koRpus in R
system.time(
  lemma_tagged <- treetag(lemma_unique$word_clean, treetagger="manual", 
                          
                          format="obj", TT.tknz=FALSE , lang="en",
                          TT.options=list(
                          path="Applications/TreeTagger", preset="en"),
                           debug=TRUE
                          )
)
        split=[[:space:]]
        ign.comp=-
        heuristics=abbr
        heur.fix=c("’", "'"), c("’", "'")
        sentc.end=., !, ?, ;, :
        detect=FALSE, FALSE
        clean.raw=
        perl=FALSE
        stopwords=
        stemmer=
 
          TT.tokenizer:  koRpus::tokenize() 
          tempfile: /var/folders/gp/rs2l_qcn4nld9bwwl_c5y5wh0000gn/T//Rtmpq07u1C/tokenize2056224d1f7b.txt 
          file:  /var/folders/gp/rs2l_qcn4nld9bwwl_c5y5wh0000gn/T//Rtmpq07u1C/tempTextFromObject20565d2ea8d0.txt 
          TT.lookup.command:   
          TT.pre.tagger:  grep -v '^$' | 
          TT.tagger:  Applications/TreeTagger/bin/tree-tagger 
          TT.opts:  -token -lemma -sgml -pt-with-lemma -quiet 
          TT.extra.opts:   
          TT.params:  /Users/zhengsongyi/Desktop/2022Spring/Capstone/test/Applications/TreeTagger/lib/english.par 
          TT.filter.command:  | perl -pe 's/\tV[BDHV]/\tVB/;s/IN\/that/\tIN/;' 

          sys.tt.call:  cat  /var/folders/gp/rs2l_qcn4nld9bwwl_c5y5wh0000gn/T//Rtmpq07u1C/tokenize2056224d1f7b.txt |  grep -v '^$' | Applications/TreeTagger/bin/tree-tagger -token -lemma -sgml -pt-with-lemma -quiet  /Users/zhengsongyi/Desktop/2022Spring/Capstone/test/Applications/TreeTagger/lib/english.par | perl -pe 's/\tV[BDHV]/\tVB/;s/IN\/that/\tIN/;' 

Assuming 'UTF-8' as encoding for the input file. If the results turn out to be erroneous, check the file for invalid characters, e.g. em.dashes or fancy quotes, and/or consider setting 'encoding' manually.
   user  system elapsed 
  0.998   0.087   1.049 
# Make sure all packages including tidyverse is loaded or you may get an error that the preset is not recognized. In particular check koRpus.lang.en

#lemma_tagged_tbl <- tbl_df(lemma_tagged@TT.res)
#lemma_tagged_tbl <- tibble(lemma_tagged@TT.res)
lemma_tagged_tbl <- as_tibble(lemma_tagged)
# replace unknowns with the old raw word
#lemma_unique <- lemma_unique %>% 
#  left_join(lemma_tagged_tbl %>%
#              filter(lemma != "<unknown>") %>%
#              select(token, lemma, wclass), by = c("word_clean" = "token")
#            ) %>%
#  arrange(word)
# replace wclass w/ tag
lemma_unique <- lemma_unique %>% 
  left_join(lemma_tagged_tbl %>%
              filter(lemma != "<unknown>") %>%
              select(token, lemma, tag), by = c("word_clean" = "token")
            ) %>%
  arrange(word)

# Replace all NA with the word in word_clean
lemma_unique = within(lemma_unique, {
  lemma = ifelse(is.na(lemma), word_clean, lemma)  } )
#replace all of the original words in the nsf_funding with lemmas
# This also needs to be done for the bigrams where the substatution happens for each of the two columns in the bigram. ???
lemma_new <- lemma_unique
#lemma_new <- left_join(nsf_funding,lemma_new, by = "word")
lemma_new <- left_join(nsf_funding,lemma_new, by = "word") %>%
  mutate(word = ifelse(is.na(lemma), word_clean, lemma)) # %>%
lemma_new <- subset(lemma_new, select = c(id, word))
lemma_new[is.na(lemma_new$word),]
NA

There should be no NA words!!! if so there is a problem with the input… UTF conversion problem.

library(tm)
Loading required package: NLP

Attaching package: ‘NLP’

The following object is masked from ‘package:ggplot2’:

    annotate


Attaching package: ‘tm’

The following object is masked from ‘package:koRpus’:

    readTagged
#myCorpus <- Corpus(VectorSource(nsf_funding$word)) 
myCorpus <- Corpus(VectorSource(lemma_new$word)) 
#create the toSpace content transformer
toSpace <- content_transformer(function(x, pattern) {return (gsub(pattern, " ", x))})

#Strip digits (std transformation, so no need for content_transformer)
myCorpus <- tm_map(myCorpus, removeNumbers)
Warning in tm_map.SimpleCorpus(myCorpus, removeNumbers) :
  transformation drops documents
dtm <- DocumentTermMatrix(myCorpus)

freq <- colSums(as.matrix(dtm))
Error: vector memory exhausted (limit reached?)
# Plot the occurence frequencies
wf=data.frame(term=names(freq),occurrences=freq)
library(ggplot2)
p <- ggplot(subset(wf, freq>20), aes(term, occurrences))
p <- p + geom_bar(stat="identity")
p <- p + theme(axis.text.x=element_text(angle=45, hjust=1))
p

#wordcloud
library(wordcloud)
Loading required package: RColorBrewer
#setting the same seed each time ensures consistent look across clouds
set.seed(42)
png(paste(topicDir,"/FreqWordCloud.png", sep=""), width=1280,height=800)
#limit words by specifying min frequency
wordcloud(names(freq),freq, max.words=100, scale=c(4,.2), colors=brewer.pal(6,"Dark2"))
while (!is.null(dev.list()))  dev.off()
#Second call puts the cloud image on the screen
wordcloud(names(freq), freq, max.words=100, scale=c(4,.2), colors=brewer.pal(6,"Dark2"))
Warning in wordcloud(names(freq), freq, max.words = 100, scale = c(4, 0.2),  :
  project could not be fit on page. It will not be plotted.

##Topic Modeling using lemmas

#word_counts <- nsf_funding %>%
word_counts <- lemma_new %>%
  #anti_join(my_stop_words) %>%
  count(id, word, sort = TRUE) %>%
  ungroup()

word_counts
#Remove numbers
#word_counts<-word_counts[-grep("\\b\\d+\\b", word_counts$word),]

desc_dtm <- word_counts %>%
  cast_dtm(id, word, n)

desc_dtm
<<DocumentTermMatrix (documents: 38, terms: 1752)>>
Non-/sparse entries: 4743/61833
Sparsity           : 93%
Maximal term length: 17
Weighting          : term frequency (tf)
#Calculate tf_idf
#word_counts <- word_counts %>%
 #bind_tf_idf(id, word, n)
#word_counts
#Continue tf_idf
#word_counts %>%
  #arrange(tf_idf)
#Calculate tf-idf
#desc_tf_idf <- nsf_funding %>% 
desc_tf_idf <- lemma_new %>% 
  count(id, word, sort = TRUE) %>%
  ungroup() %>%
  bind_tf_idf(word, id, n)

#New document term matrix for topic modeling with custom stop words #??? rather than low idf use low tf_idf - may be replaced with a trim of the word list by ft-idf low_idf <- subset(desc_tf_idf, idf>0.38 & idf<2) #topicFileName <- gsub(“.csv”,“low_idf.csv”, filename) write.csv(low_idf,paste(topicDir,“/low_idf.csv”, sep=““))

#What are highest tf-idf words?
desc_tf_idf %>% 
  arrange(-tf_idf)
#What are lowest idf words?
desc_tf_idf %>% 
  arrange(idf)
freq <- colSums(as.matrix(desc_dtm))

#create sort order (descending)
ord <- order(freq,decreasing=TRUE)

#inspect most frequently occurring terms
freq[head(ord)]
  project     datum  research    system algorithm   network 
      116       101        82        80        74        66 
#inspect least frequently occurring terms
freq[tail(ord)]
transaction     tunable      update      usable        weak  workaround 
          1           1           1           1           1           1 
#Ignore this maybe??? do not use.
#wf=data.frame(term=names(freq),occurrences=freq)
#library(ggplot2)
#topicFileName <- gsub(".csv","tf-idfhistogram.png", filename)
#p <- ggplot(subset(wf, freq>250), aes(term, occurrences))
#p <- p + geom_bar(stat="identity")
#p <- p + theme(axis.text.x=element_text(angle=45, hjust=1))
#p
#ggsave(topicFileName, device = "png")

#wordcloud
library(wordcloud)
#setting the same seed each time ensures consistent look across clouds
set.seed(42)
png(paste(topicDir,"/TF-IDFWordCloud.png", sep=""), width=1280,height=800)
#limit words by specifying min frequency
#wordcloud(names(freq),freq, min.freq=80,colors=brewer.pal(6,"Dark2"))
wordcloud(names(freq),freq, min.freq=20,scale=c(4,.2), colors=brewer.pal(6,"Dark2"))
while (!is.null(dev.list()))  dev.off()
wordcloud(names(freq),freq, min.freq=20,scale=c(4,.2), colors=brewer.pal(6,"Dark2"))
Warning in wordcloud(names(freq), freq, min.freq = 20, scale = c(4, 0.2),  :
  project could not be fit on page. It will not be plotted.

https://cran.r-project.org/web/packages/ldatuning/vignettes/topics.html Select number of topics for LDA model Murzintcev Nikita 2016-10-24

Package can be installed from CRAN Package ldatuning realizes 4 metrics to select perfect number of topics for LDA model.

#LDA Tuning: Pick the Number of topics
Warning message:
restarting interrupted promise evaluation 
#install.packages("ldatuning")
# Changed method in the exmaple from "Gibbs"" to "VEM" to mathc the Nasa Example
library("ldatuning")
library(topicmodels)
system.time(
  result <- FindTopicsNumber(
  desc_dtm,
  topics = seq(from = 10, to = 50, by = 1),
  metrics = c("Griffiths2004", "CaoJuan2009", "Arun2010", "Deveaud2014"),
  method = "Gibbs",
  control = list(seed = 1234),
  mc.cores = 4L,
  verbose = TRUE
))
fit models... done.
calculate metrics:
  Griffiths2004... done.
  CaoJuan2009... done.
  Arun2010... done.
  Deveaud2014... done.
   user  system elapsed 
  3.240   0.367  41.289 
png(paste(topicDir,"/FindTopicNumber.png", sep=""), width=1280,height=800)
FindTopicsNumber_plot(result)

while (!is.null(dev.list()))  dev.off()
FindTopicsNumber_plot(result)

Gibbs method consensus shows a peak of 23 for the DEB data Gibbs method consensus shows a peak of 30 for the AST data

harmonicMean <- function(logLikelihoods, precision = 2000L) {
  llMed <- median(logLikelihoods)
  as.double(llMed - log(mean(exp(-mpfr(logLikelihoods,
                                       prec = precision) + llMed))))
}
seqk <- seq(2, 100, 1)
burnin <- 1000
iter <- 1000
keep <- 50
system.time(fitted_many <- lapply(seqk, function(k) topicmodels::LDA(desc_dtm, k = k,
                                                     method = "Gibbs",control = list(burnin = burnin,
                                                                         iter = iter, keep = keep) )))
   user  system elapsed 
299.010   1.881 302.937 
roundto10 <- function(x) ceiling(max(x)/10)*10
# extract logliks from each topic
library("Rmpfr")
Loading required package: gmp

Attaching package: ‘gmp’

The following objects are masked from ‘package:base’:

    %*%, apply, crossprod, matrix, tcrossprod

C code of R package 'Rmpfr': GMP using 64 bits per limb


Attaching package: ‘Rmpfr’

The following object is masked from ‘package:gmp’:

    outer

The following objects are masked from ‘package:stats’:

    dbinom, dgamma, dnbinom, dnorm, dpois, dt, pnorm

The following objects are masked from ‘package:base’:

    cbind, pmax, pmin, rbind
logLiks_many <- lapply(fitted_many, function(L)  L@logLiks[-c(1:(burnin/keep))])

# compute harmonic means
hm_many <- sapply(logLiks_many, function(h) harmonicMean(h))

#Now that is have calculated the harmonic means of the models for 2 - 100 topics, you can plot the results using ggplot2 package.

require(scales)
Loading required package: scales

Attaching package: ‘scales’

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor
ldaplot <- ggplot(data.frame(seqk, hm_many), aes(x=seqk, y=hm_many)) + geom_path(lwd=1.5) +
  theme(text = element_text(family= NULL),
        axis.title.y=element_text(vjust=1, size=16),
        axis.title.x=element_text(vjust=-.5, size=16),
        axis.text=element_text(size=16),
        plot.title=element_text(size=20)) +
  scale_y_continuous(labels = comma) +
  xlab('Number of Topics') +
  ylab('Harmonic Mean') +
  ggplot2::annotate("text", x = 25, y = roundto10(max(hm_many)+10000), # calculate the max rather than hard code ???
           label = paste("The optimal number of topics is", seqk[which.max(hm_many)])) +
  ggtitle(expression(atop("LDA Analysis of NSF Program", 
                          atop(italic("Harmonic Mean"), ""))))
 
png(paste(topicDir,"/LDAHamonicMean.png", sep=""), width=1280,height=800)
ldaplot
while (!is.null(dev.list()))  dev.off()
ldaplot

#Harmonic mean above has optimum of 20 for combined collaborations in astronomy. Prior analysis used 22 but the peak is about the same.
#Inspired by: https://www.tidytextmining.com/nasa.html#topic-modeling
library(topicmodels)

#Harmonic mean was 34 topics but 29 is almost the same; 
# [23] -408680.1 -407910.1 -408071.9 -407422.7 -407173.9 -406342.0 -407583.3 -407614.1 -407782.2 -408115.3 -405507.6
# be aware that running this model is time intensive
NumTopics <- 18
desc_lda <- LDA(desc_dtm, method = "Gibbs", k = NumTopics, control = list(seed = 1234))
desc_lda
A LDA_Gibbs topic model with 18 topics.
# Since it is time consuming to create the desc_lda file we save it here for backup. 
saveRDS(desc_lda, file = paste(topicDir,"/desc_lda.rds", sep = ""))
# to get it back desc_lda <- load(paste(topicDir,"/desc_lda.RData"))
library(tidytext)
tidy_lda <- tidy(desc_lda)

tidy_lda
#Examine top 10 terms for each topic
top_terms <- tidy_lda %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  ungroup() %>%
  arrange(topic, -beta)

top_terms

#https://tm4ss.github.io/docs/Tutorial_6_Topic_Models.html

top10termsPerTopic <- terms(desc_lda, 10)
topicNames <- apply(top10termsPerTopic, 2, paste, collapse=" ")

# have a look at some of the results (posterior distributions)
tmResult <- posterior(desc_lda)
library(wordcloud)
# visualize topics as word cloud
#for (TopicNum in 1:NumTopics){
for (TopicNum in 1:18){
  set.seed(42)
  topicToViz <- TopicNum # change for your own topic of interest
  #topicToViz <- 1 # change for your own topic of interest
  #topicToViz <- grep('telescope', topicNames)[1] # Or select a topic by a term contained in its name
  # select to 100 most probable terms from the topic by sorting the term-topic-probability vector in decreasing order
  top100terms <-
  sort(tmResult$terms[topicToViz, ], decreasing = TRUE)[1:100]
  words <- names(top100terms)
  # extract the probabilites of each of the 100 terms
  probabilities <-
  sort(tmResult$terms[topicToViz, ], decreasing = TRUE)[1:100]
  # visualize the terms as wordcloud
  mycolors <- brewer.pal(8, "Dark2")
  #topicFileName <- gsub(".csv", paste("Topic", toString(TopicNum), "WordCloud.png", sep = ""), filename) 
  png(paste(topicDir,"/WordCloud.png", sep=""), width = 1280, height = 800)
  while (!is.null(dev.list()))  dev.off()
  wordcloud(
  words,
  probabilities,
  scale = c(4, .2),
  random.order = FALSE,
  color = mycolors
  )
  dev.off()
  wordcloud(
  words,
  probabilities,
  scale = c(4, .2),
  random.order = FALSE,
  color = mycolors
  )
}
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  development could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  accurately could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  computationally could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  discover could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  primitive could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  modeling could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  difficult could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  source could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  compact could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  threshold could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  combinatorial could not be fit on page. It will not be plotted.

Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  computational could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  accurately could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  computationally could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  transcriptome could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  speed could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  proposal could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  compact could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  threshold could not be fit on page. It will not be plotted.
Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  optimal could not be fit on page. It will not be plotted.

Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  communication could not be fit on page. It will not be plotted.

Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  communication could not be fit on page. It will not be plotted.

Warning in wordcloud(words, probabilities, scale = c(4, 0.2), random.order = FALSE,  :
  performance could not be fit on page. It will not be plotted.

library(ggplot2)
png(paste(topicDir,"/TermsLDAHistogram.png", sep=""), width = 1280, height = 1600)
top_terms %>%
  mutate(term = reorder(term, beta)) %>%
  group_by(topic, term) %>%    
  arrange(desc(beta)) %>%  
  ungroup() %>%
  mutate(term = factor(paste(term, topic, sep = "__"), 
                       levels = rev(paste(term, topic, sep = "__")))) %>%
  ggplot(aes(term, beta, fill = as.factor(topic))) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  scale_x_discrete(labels = function(x) gsub("__.+$", "", x)) +
  labs(title = "Top terms in each LDA topic",
       x = NULL, y = expression(beta)) +
  facet_wrap(~ topic, ncol = 3, scales = "free")
#topicFileName <- gsub(".csv","TermsLDAHistogram.png", filename)
#ggsave(topicFileName, height = 14, device = "png")
#png(paste(topicDir,"/TermsLDAHistogram.png", sep=""), width = 1280, height = 1600)
while (!is.null(dev.list()))  dev.off()
# Calculate the top terms in each topic
top_terms %>%
  mutate(term = reorder(term, beta)) %>%
  group_by(topic, term) %>%    
  arrange(desc(beta)) %>%  
  ungroup() %>%
  mutate(term = factor(paste(term, topic, sep = "__"), 
                       levels = rev(paste(term, topic, sep = "__")))) %>%
  ggplot(aes(term, beta, fill = as.factor(topic))) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  scale_x_discrete(labels = function(x) gsub("__.+$", "", x)) +
  labs(title = "Top terms in each LDA topic",
       x = NULL, y = expression(beta)) +
  facet_wrap(~ topic, ncol = 3, scales = "free")

#Top 10 terms in each model

terms(desc_lda, 10)
      Topic 1      Topic 2      Topic 3      Topic 4       Topic 5        Topic 6      Topic 7     Topic 8    Topic 9       
 [1,] "method"     "datum"      "learn"      "student"     "linear"       "design"     "project"   "design"   "compute"     
 [2,] "develop"    "cluster"    "training"   "software"    "algebraic"    "auction"    "analysis"  "base"     "application" 
 [3,] "approach"   "privacy"    "research"   "research"    "method"       "resource"   "technique" "software" "parallel"    
 [4,] "datum"      "machine"    "algorithm"  "reliability" "project"      "guarantee"  "tool"      "system"   "architecture"
 [5,] "gene"       "address"    "run"        "provide"     "construction" "allocation" "market"    "approach" "emerge"      
 [6,] "tool"       "analysis"   "connection" "science"     "tool"         "mechanism"  "algorithm" "analysis" "program"     
 [7,] "result"     "future"     "efficient"  "university"  "dimension"    "objective"  "field"     "tradeoff" "research"    
 [8,] "expression" "discovery"  "task"       "include"     "pseudorandom" "require"    "question"  "space"    "fault"       
 [9,] "cell"       "framework"  "improve"    "safety"      "polynomial"   "art"        "develop"   "level"    "gpu"         
[10,] "technology" "researcher" "machine"    "reu"         "set"          "agent"      "model"     "complex"  "include"     
      Topic 10      Topic 11        Topic 12      Topic 13        Topic 14    Topic 15        Topic 16      Topic 17     
 [1,] "network"     "algorithm"     "graph"       "theory"        "support"   "datum"         "system"      "system"     
 [2,] "develop"     "optimization"  "network"     "information"   "impact"    "network"       "project"     "efficiency" 
 [3,] "clock"       "focus"         "processing"  "study"         "time"      "communication" "device"      "energy"     
 [4,] "topology"    "framework"     "datum"       "project"       "broad"     "code"          "batteryless" "memory"     
 [5,] "performance" "student"       "social"      "science"       "signal"    "scheme"        "computer"    "performance"
 [6,] "project"     "submodular"    "challenge"   "understanding" "deep"      "research"      "outreach"    "propose"    
 [7,] "scientific"  "algorithmic"   "research"    "interactive"   "domain"    "caching"       "reliable"    "hardware"   
 [8,] "tree"        "modern"        "practice"    "community"     "structure" "practical"     "energy"      "scale"      
 [9,] "circuit"     "aim"           "cost"        "measurement"   "property"  "information"   "student"     "solution"   
[10,] "mode"        "undergraduate" "interaction" "compression"   "model"     "storage"       "industry"    "computer"   
      Topic 18        
 [1,] "language"      
 [2,] "programme"     
 [3,] "client"        
 [4,] "implementation"
 [5,] "component"     
 [6,] "interface"     
 [7,] "programmer"    
 [8,] "synthesis"     
 [9,] "program"       
[10,] "exist"         
##Examine which topics are associated with which documents

lda_gamma <- tidy(desc_lda, matrix = "gamma")

lda_gamma

library(data.table)
data.table 1.13.6 using 1 threads (see ?getDTthreads).  Latest news: r-datatable.com
**********
This installation of data.table has not detected OpenMP support. It should still work but in single-threaded mode.
This is a Mac. Please read https://mac.r-project.org/openmp/. Please engage with Apple and ask them for support. Check r-datatable.com for updates, and our Mac instructions here: https://github.com/Rdatatable/data.table/wiki/Installation. After several years of many reports of installation problems on Mac, it's time to gingerly point out that there have been no similar problems on Windows or Linux.
**********

Attaching package: ‘data.table’

The following object is masked from ‘package:purrr’:

    transpose

The following objects are masked from ‘package:dplyr’:

    between, first, last
# Change the index to be a column. nsf_funding_save is in funding order.
setDT(nsf_funding_save, keep.rownames = TRUE)[]
#Change the column name form the "rn" defaculty to "FundingRank"
colnames(nsf_funding_save)[1] <- "FundingRank"
# Change class of nsf_funding_save$id to charcater(from integer)
nsf_funding_save$id <- as.character(nsf_funding_save$id)
lda_gamma2 <- dplyr::left_join(lda_gamma, nsf_funding_save, 
              select(FundingRank), by = c("document" = "id")) 
# convert FundingRank to a number
lda_gamma2$FundingRank = as.numeric(lda_gamma2$FundingRank)#[lda_gamma2]
# convert Topic to a character

# Sort the topics by the amont of monney they braught in. Sum of document Gamma = 1
# The funding associated with each topic is the 
# sum of all (gamma*funding amount for each grant)
lda_gamma2 <- lda_gamma2 %>% mutate(worth = gamma * amount)
# calculate the sum of the worth for each topic
topic_worth <- aggregate(lda_gamma2$worth, by=list(topic = lda_gamma2$topic), FUN = "sum")
#topic_worth <- aggregate(lda_gamma2$worth, by=list(topic = as.character(lda_gamma2$topic)), FUN = "sum")
colnames(topic_worth)[2] <- "WorthSum"
# sort by most worthy of topic
topic_worth <- topic_worth %>% arrange(desc(WorthSum))
# save topic_worth as CVS

setDT(topic_worth, keep.rownames = "TopicWorthRank")[]

lda_gamma3 <- left_join(lda_gamma2, topic_worth, 
              select(x, TopicWorthRank), by = "topic") 
lda_gamma3$TopicWorthRank = as.numeric(lda_gamma3$TopicWorthRank)

p <- ggplot(lda_gamma3, aes(x=FundingRank, y=TopicWorthRank)) +
    geom_point(aes(size=gamma), shape = 17) +   # draw points
    scale_size_continuous(limits=c(.2,1)) +
    xlim(c(0, length(nsf_funding_save$id))) + 
   ylim(c(1, NumTopics)) +
   labs(subtitle="Topic by Document", 
       y="Topic #", 
       x="Document #", 
       title="Gamma Level sorted by Funding Level", 
       caption="Topic Analysis")
p
Warning: Removed 648 rows containing missing values (geom_point).

ggsave(paste(topicDir,"/DocTopic.png", sep=""), height = 10, device = "png")
Saving 20 x 10 in image
Warning: Removed 648 rows containing missing values (geom_point).
#png(paste(topicDir,"/TermsLDAHistogram.png", sep=""), width = 1280, height = 1600)
while (!is.null(dev.list()))  dev.off()
p
Warning: Removed 648 rows containing missing values (geom_point).


#Join funding amount to data
nsf_funding_join <- nsf_funding_save
nsf_funding_join$document <- as.character(nsf_funding_join$id)

funding_gamma <- merge(lda_gamma, nsf_funding_join, by="document", all.x = TRUE)
  funding_gamma <- subset(funding_gamma, select=-c(id, abstract))
  
funding_gamma
NA
#Need to join lda_gamma with funding amount vector, that would be informative
doc_top <- funding_gamma %>%
  group_by(document) %>%
#  top_n(1, gamma) %>%
  top_n(5, gamma) %>%
  ungroup() %>%
#  arrange(gamma)
  arrange(document, desc(gamma))
write.csv(doc_top, paste(topicDir,"/topTopics5Documents.csv", sep=""))

doc_top
#Consensus document for each topic
document_topics <- doc_top %>%
  count(document, topic, amount) %>%
  group_by(topic) %>%
  top_n(1, n) %>%
  ungroup() %>%
  transmute(consensus = document, topic, amount)

document_topics
#See which documents misidentified, this is probably not useful for us, since documents can easily be in multiple topics
doc_top %>%
  inner_join(document_topics, by = "topic") %>%
  count(document, consensus)
p
Warning: Removed 684 rows containing missing values (geom_text).

NO NOT USE THIS CHUNK. Use the next one to make lda_json for use in LDAvis package

#In this chunk I was trying to use LDAvis package, but was unable to convert topicmodels output

#install.packages(“LDAvis”) #devtools::install_github(“cpsievert/LDAvisData”)

#’ Convert the output of a topicmodels Latent Dirichlet Allocation to JSON #’ for use with LDAvis #’ #’ @param fitted Output from a topicmodels model. = desc_lda #’ @param corpus Corpus object used to create the document term #’ matrix for the model. This should have been create with #’ the tm package’s function. = myCorpus #’ @param doc_term The document term matrix used in the #’ model. This should have been created with the tm package’s #’ function. = dtm #’ #’ @seealso . #’ @export

topicmodels_json_ldavis <- function(fitted, corpus, doc_term){ # Required packages library(topicmodels) library(dplyr) library(stringi) library(tm) library(LDAvis)

# Find required quantities

phi <- posterior(fitted)\(terms %>% as.matrix theta <- posterior(fitted)\)topics %>% as.matrix vocab <- colnames(phi) doc_length <- vector() for (i in 1:length(corpus)) { temp <- paste(corpus[[i]]$content, collapse = ’ ‘) doc_length <- c(doc_length, stri_count(temp, regex =’\S+’)) }

temp_frequency <- as.matrix(doc_term) freq_matrix <- data.frame(ST = colnames(temp_frequency), Freq = colSums(temp_frequency)) rm(temp_frequency)

# Convert to json

json_lda <- LDAvis::createJSON(phi = phi, theta = theta, vocab = vocab, doc.length = doc_length, term.frequency = freq_matrix$Freq)

return(json_lda) }

lda_json <- topicmodels_json_ldavis(fitted = desc_lda, corpus = myCorpus, doc_term = dtm)

# This chink does a multidimentional scaling intertopic distance map.
    # Required packages
    library(topicmodels)
    library(dplyr)
    library(stringi)
    library(tm)
    library(LDAvis)
    library(devtools)
Loading required package: usethis
devtools::install_github("cpsievert/LDAvisData")
Skipping install of 'LDAvisData' from a github remote, the SHA1 (43dd263a) has not changed since last install.
  Use `force = TRUE` to force installation
devtools::install_github("cpsievert/LDAvis")
Skipping install of 'LDAvis' from a github remote, the SHA1 (5067f7b8) has not changed since last install.
  Use `force = TRUE` to force installation
#' Transform Model Output for Use with the LDAvis Package
#' https://gist.github.com/trinker/477d7ae65ff6ca73cace
#' Convert a \pkg{topicmodels} output into the JSON form required by the \pkg{LDAvis} package.
#'
#' @param model A \code{\link[]{topicmodel}} object.
#' @param \ldots Currently ignored.
#' @seealso \code{\link[LDAvis]{createJSON}}
#' @export
#' @examples
#' \dontrun{
#' data("AssociatedPress", package = "topicmodels")
#' model <- LDA(AssociatedPress[1:20,], control = list(alpha = 0.1), k = 3)
#' LDAvis::serVis(topicmodels2LDAvis(model))
#' }
topicmodels2LDAvis <- function(x, ...){
    post <- topicmodels::posterior(x)
    if (ncol(post[["topics"]]) < 3) stop("The model must contain > 2 topics")
    mat <- x@wordassignments
    LDAvis::createJSON(
        phi = post[["terms"]], 
        theta = post[["topics"]],
        vocab = colnames(post[["terms"]]),
        doc.length = slam::row_sums(mat, na.rm = TRUE),
        term.frequency = slam::col_sums(mat, na.rm = TRUE)
    )
}
lda_json <- topicmodels2LDAvis(desc_lda)
serVis(lda_json, reorder.topic = TRUE)
Loading required namespace: servr
Failed with error:  ‘there is no package called ‘servr’’
If the visualization doesn't render, install the servr package
and re-run serVis: 
 install.packages('servr') 
Alternatively, you could configure your default browser to allow
access to local files as some browsers block this by default
library(RJSONIO)
x <- fromJSON(lda_json)
 
### this will map the tpic numbers from the rest of the analysis to the multidimentional scaling topic numbers.
print("Original - MDS#")
[1] "Original - MDS#"
for( i in 1:NumTopics) {
  print(paste(i, which(x$topic.order == i)))
}
[1] "1 16"
[1] "2 17"
[1] "3 12"
[1] "4 13"
[1] "5 18"
[1] "6 15"
[1] "7 7"
[1] "8 14"
[1] "9 4"
[1] "10 11"
[1] "11 8"
[1] "12 5"
[1] "13 9"
[1] "14 1"
[1] "15 2"
[1] "16 6"
[1] "17 3"
[1] "18 10"

# Generate bigrams

# Bigram setup
library(tidyr)
#nsf_funding_bigram %>%
#  count(bigram, sort = TRUE)
# remove bigrams with stopwords

bigrams_separated <- nsf_funding_bigram %>%
  separate(bigram, c("word1", "word2"), sep = " ")

bigrams_filtered <- bigrams_separated %>%
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word)#replace all of the original words in the nsf_funding with lemmas
# This also needs to be done for the bigrams where the substatution happens for each of the two columns in the bigram. ???
lemma_bigram_new <- lemma_unique
#replace word 1 with lemma
lemma_bigram_new <- left_join(bigrams_filtered,lemma_bigram_new, by = c("word1" = "word")) %>%
  mutate(word1 = ifelse(is.na(lemma), word_clean, lemma)) # %>%
# drop last three columns id, word1 and word2
lemma_bigram_new <- subset(lemma_bigram_new, select = c(id, word1, word2))
lemma_bigram_new <- left_join(lemma_bigram_new, lemma_unique, by = c("word2" = "word")) %>%
  mutate(word2 = ifelse(is.na(lemma), word_clean, lemma)) # %>%

lemma_bigram_new <- subset(lemma_bigram_new, select = c(id, word1, word2))
#nsf_funding <- subset()

# new bigram counts:
bigram_counts <- lemma_bigram_new %>% 
  count(word1, word2, sort = TRUE)

bigram_counts
NA

ala bernhardlearns.com To speed things up, we will not work on every word instance but filter out only the unique ones. If needed the results for the unique word instance can be easily mapped back to the larger data-frame. Some of the techniques I am going to present are quite computational expansive, so if you have a much larger data set, then maybe they are not feasible. I have added system.time() to the particular work-steps so that you can see and decide for yourself.

Further, we apply some basic cleaning:

removing the possessive ending: ’s
removing all words containing non alphabetic characters (depending on the task at hand this might be a bad idea - e.g., in social media emoticons can be very informative)
removing missing values
bigrams_united <- lemma_bigram_new %>%
  unite(bigram, word1, word2, sep = " ")
bigrams_united %>%
  count(bigram, sort = TRUE)
#

https://www.tidytextmining.com/ngrams.html

library(igraph)

bigram_graph <- bigram_counts %>%
  filter(n > 6) %>%
  graph_from_data_frame()

library(ggraph)
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1)

#topicFileName <- gsub(".csv","bigraph.png", filename)
#png(topicFileName, width = 12, height = 8, units = 'in', res = 300)
png(paste(topicDir,"/bigraph.png", sep=""), width = 12, height = 8, units = 'in', res = 300)
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1)
dev.off()
quartz_off_screen 
                2 

TF-IDF Analysis of bigrams

bigram_tf_idf <- bigrams_united %>%
  count(id, bigram) %>%
  bind_tf_idf(bigram, id, n) %>%
  arrange(desc(tf_idf))

bigram_tf_idf
# Graph the bigrams. THIS WILL TAKE SOME TIME
# categories are compressed
p <- bigram_tf_idf %>%
  arrange(desc(tf_idf)) %>%
  mutate(bigram = factor(bigram, levels = rev(unique(bigram)))) %>% 
  group_by(id) %>% 
  top_n(5) %>% 
  ungroup %>%
  ggplot(aes(bigram, tf_idf, fill = id)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~id, ncol = 2, scales = "free") +
  coord_flip()
Selecting by tf_idf
#topicFileName <- gsub(".csv","BigramTFIDFHistogram.png", filename)
#ggsave(topicFileName, height = 14, device = "png")
p
ggsave(paste(topicDir,"/BigramTFIDFHistogram.png", sep=""), height = 30, device = "png")
Saving 20 x 30 in image
#png(paste(topicDir,"/TermsLDAHistogram.png", sep=""), width = 1280, height = 1600)
while (!is.null(dev.list()))  dev.off()
#Top documents per topic
library(ggplot2)
library(ggrepel)

number_of_documents = 5 #number of top docs to view
title <- paste("LDA Top Documents for", NumTopics, "Topics")

top_documents <- lda_gamma %>%
  group_by(topic) %>%
  arrange(topic, desc(gamma)) %>%
  slice(seq_len(number_of_documents)) %>%
  arrange(topic, gamma) %>%
  mutate(row = row_number()) %>%
  ungroup() #%>%
  #re-label topics
  #mutate(topic = paste("Topic", topic, sep = " "))

#topicFileName <- gsub(".csv","top_documents.csv", filename)
write.csv(top_documents, paste(topicDir,"/top_documents.csv", sep=""))

top_bigrams_for_docs <- bigram_tf_idf %>%
  group_by(id) %>%
  arrange(id, desc(tf_idf)) %>%
  slice(seq_len(number_of_documents)) %>%
  arrange(id, tf_idf) %>%
  ungroup()  %>%
  select(id, bigram) 
 
# make the 5 bigrams into 5 columns
top_bigrams_for_docs_wide <- 
  dcast(setDT(top_bigrams_for_docs), id~rowid(id, prefix="bigram"), value.var="bigram")   

# add the bigram columns to top_docs
### error - repeats of bigrams for too many documents....
# maybe not an error... documents in the smae topic are getting many of the same bigrams.
top_documents <- left_join(top_documents, top_bigrams_for_docs_wide,
            select(bigram1, bigram2, bigram3, bigram4, bigram5),
            by = c("document" = "id"))
#Join funding amount and title to top_docs_titles

nsf_funding_titles <- data_frame(document = MyData$AwardNumber,
                          title = MyData$Title, amount = MyData$AwardedAmountToDate)
# Convert the document #/grant number to a charctaer sting  
nsf_funding_titles$document <- as.character(nsf_funding_titles$document)

top_documents <- full_join(top_documents, nsf_funding_titles, by = "document")
#top_docs_titles <- merge(top_documents, nsf_funding_titles, by="document", all.x = TRUE)
#top_docs_titles <- subset(top_docs_titles, select=-c(row))

#top_documents <- full_join(top_documents, top_docs_titles, by = "document")
# add topic worth and rank
top_documents <- full_join(top_documents, topic_worth, by = "topic")

#topicFileName <- gsub(".csv","top_documents.csv", filename)
write.csv(top_documents, paste(topicDir,"/top_documents.csv", sep=""))

#top_docs_titles
#Top documents per topic


#FIGURE IS BAD AND NEEDS WORK, NOT REALLY A USEFUL VISUALIZATION ANYWAY
title <- paste("LDA Top Documents for", NumTopics, "Topics")

#define some colors to use throughout
my_colors <- c("#E69F00", "#56B4E9", "#009E73", "#CC79A7", "#D55E00", "#D65E00")

#customize ggplot2's default theme settings
#this tutorial doesn't actually pass any parameters, but you may use it again in future tutorials so it's nice to have the options
theme_lyrics <- function(aticks = element_blank(),
                         pgminor = element_blank(),
                         lt = element_blank(),
                         lp = "none")
{
  theme(plot.title = element_text(hjust = 0.5), #center the title
        axis.ticks = aticks, #set axis ticks to on or off
        panel.grid.minor = pgminor, #turn on or off the minor grid lines
        legend.title = lt, #turn on or off the legend title
        legend.position = lp) #turn on or off the legend
}

#customize the text tables for consistency using HTML formatting
my_kable_styling <- function(dat, caption) {
  kable(dat, "html", escape = FALSE, caption = caption) %>%
  kable_styling(bootstrap_options = c("striped", "condensed", "bordered"),
                full_width = FALSE)
}

word_chart <- function(data, input, title) {
  data %>%
  #set y = 1 to just plot one variable and use word as the label
  ggplot(aes(as.factor(row), 1, label = input, fill = factor(topic) )) +
  #you want the words, not the points
  geom_point(color = "transparent") +
  #make sure the labels don't overlap
  geom_label_repel(nudge_x = .2,  
                   direction = "y",
                   box.padding = 0.1,
                   segment.color = "transparent",
                   size = 3) +
  facet_grid(~topic) +
  theme_lyrics() +
  theme(axis.text.y = element_blank(), axis.text.x = element_blank(),
        #axis.title.x = element_text(size = 9),
        panel.grid = element_blank(), panel.background = element_blank(),
        panel.border = element_rect("lightgray", fill = NA),
        strip.text.x = element_text(size = 9)) +
  labs(x = NULL, y = NULL, title = title) +
    #xlab(NULL) + ylab(NULL) +
  #ggtitle(title) +
  coord_flip()

}

facet_wrap(~ topic, ncol = 10, scales = "free")
<ggproto object: Class FacetWrap, Facet, gg>
    compute_layout: function
    draw_back: function
    draw_front: function
    draw_labels: function
    draw_panels: function
    finish_data: function
    init_scales: function
    map_data: function
    params: list
    setup_data: function
    setup_params: function
    shrink: TRUE
    train_scales: function
    vars: function
    super:  <ggproto object: Class FacetWrap, Facet, gg>
word_chart(top_documents, top_documents$document, title)

NA
NA
#Distribution of probabilities for all topics

ggplot(lda_gamma, aes(gamma)) +
  geom_histogram() +
  scale_y_log10() +
  labs(title = "Distribution of probabilities for all topics",
       y = "Number of documents", x = expression(gamma))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Warning: Transformation introduced infinite values in continuous y-axis
Warning: Removed 5 rows containing missing values (geom_bar).

#Probability distribution in each topic

ggplot(lda_gamma, aes(gamma, fill = as.factor(topic))) +
  geom_histogram(show.legend = FALSE) +
  facet_wrap(~ topic, ncol = 4) +
  scale_y_log10() +
  labs(title = "Distribution of probability for each topic",
       y = "Number of documents", x = expression(gamma))
Error in ggplot(lda_gamma, aes(gamma, fill = as.factor(topic))) : 
  could not find function "ggplot"

https://knowledger.rbind.io/post/topic-modeling-using-r/ We can correlate the topic by the metadata available, here will will use the predefined category of the documents. I construct a dataframe binding the Document Number, theta for the document, and its associated category. The column means of theta, grouped by category is calculated.

theta <- posterior(desc_lda)$topics %>% as.matrix
x <- as.data.frame(row.names(theta), stringsAsFactors = FALSE)
colnames(x) <- c("GrantNumber")
x$LessonId <- as.numeric(x$LessonId)
Error in `$<-.data.frame`(`*tmp*`, LessonId, value = numeric(0)) : 
  replacement has 0 rows, data has 38

Financial analysis on Award Amount

#Join abstracts to funding amount
library(dplyr)

nsf_amount <- data_frame(id = as.character(MyData$AwardNumber),
                            title = MyData$AwardedAmountToDate)
nsf_amount

#Continue join

nsf_amount <- nsf_amount %>% 
  unnest_tokens(word, title) %>% 
  anti_join(stop_words)
Joining, by = "word"
desc_tf_idf <- full_join(desc_tf_idf, nsf_amount, by = "id")
desc_tf_idf

# 
TopicInvestmentBigram <- full_join(top_documents, topic_worth, by = "topic")
write.csv(TopicInvestmentBigram, paste(topicDir,"/TopicInvestmentBigram.csv", sep=""))

Identify the top element codes for a topic. Each grant has a gamma associated with each topic. Each grant has one or more element code. Program codes indicate cross-NSF initiatives. The element code is home but funding also arrises from the cross NSF programs. For a first aproximation analaysis calculate the element contribution to a topic by multiplying the grant amount for each grant in the for each topic ProgramInvestment = Sum of gamma * amount divided evenly among elment codes. This distribution is an approximation because the split for funding may not be 50/50. eg. if there is one element code all spending is assigned to that one element. If there are two the investement will be split between them. Money may actually be coming form program codes as well.. Output Data Structure: Topic, Element Code, Total Investment

LS0tCnRpdGxlOiAiTlNGLUFTVCBhbmQgREVCIHRvcGljIG1vZGVsaW5nIC0gaGVhZCIKb3V0cHV0OiBodG1sX25vdGVib29rCgotLS0KVXNlZnVsIHRvb2xzLi4uIAp0byBmaW5kIE5BIGluIGEgY29sdW1uIG9mIGEgZGF0YSBmcmFtZTogbmV3X0RGIDwtIERGW2lzLm5hKERGJFZhciksXSAKZWcuIG5ld19sZW1tYV9uZXcgPC0gbGVtbWFfbmV3W2lzLm5hKGxlbW1hX25ldyR3b3JkKSxdCmBgYHtyfQpsaWJyYXJ5KHN0eWxlcikKCiMjIyMgRnVuY3Rpb25zICMjIyMjCgojLS0tLS0tLS0tLS0tLS0tLS0tLS0KcmVhZHBlcmNlbnQgPC0gZnVuY3Rpb24oKQp7IAogIG4gPC0gcmVhZGxpbmUocHJvbXB0PSJFbnRlciBhIHBlcmNlbnQsIHBvc2l0aXZlIG51bWJlciBzZWxlY3RzIGhlYWQgJiBuZWdhdGl2ZSBzZWxlY3RzIHRhaWxbLTEwMC0xMDBdOiAiKQogIGlmKCEoZ3JlcGwoIl4tP1swLTldKyQiLG4pKSB8fCBhcy5pbnRlZ2VyKG4pID4gMTAwIHx8IGFzLmludGVnZXIobikgPCAtMTAwKSAjCiAgewogICAgcmV0dXJuKHJlYWRwZXJjZW50KCkpCiAgfQogIHJldHVybihhcy5pbnRlZ2VyKG4pKQp9CgojLS0tLS0tLS0tLS0tLS0tLS0tLS0KSGlzdG9ncmFtRnVuPC1mdW5jdGlvbihmdW5kaW5nLCBmc3RhdGVkUGVyY2VudCwgbnVtUm93cykgewogIGNhdCgidHlwZW9mIGZ1bmRpbmciLHR5cGVvZihmdW5kaW5nKSwgInR5cGVvZiBucm93cyIsIHR5cGVvZihudW1Sb3dzKSwgIm5yb3dzIGZ1bmRpbmcgIiwgdHlwZW9mKG51bVJvd3MpLCAibnVtUm93czo6IiwgbnVtUm93cywgIlxuIikKICBpZiAoZnN0YXRlZFBlcmNlbnQgPCAwKSB7CiAgICBjb2xvcjEgPC0gImdyYXkiOyAKICAgIGNvbG9yMiA8LSAiZ3JheTQwIjsKICAgIG5JbkhlYWQgPC0gYXMuaW50ZWdlcigoKDEwMCArIGZzdGF0ZWRQZXJjZW50KS8xMDApICogbnVtUm93cykKICAgIG5JblRhaWwgPC0gbnVtUm93cyAtIG5JbkhlYWQKICAgIGhlYWRQZXJjZW50IDwtIGZzdGF0ZWRQZXJjZW50ICsgMTAwCiAgICB0YWlsUGVyY2VudCA8LSBhYnMoZnN0YXRlZFBlcmNlbnQpCiAgfSBlbHNlIHsKICAgIGNvbG9yMiA8LSAiZ3JheSIKICAgIGNvbG9yMSA8LSAiZ3JheTQwIgogICAgbkluSGVhZCA8LSBhcy5pbnRlZ2VyKChmc3RhdGVkUGVyY2VudC8xMDApICogbnVtUm93cykKICAgIG5JblRhaWwgPC0gbnVtUm93cyAtIG5JbkhlYWQKICAgIGhlYWRQZXJjZW50IDwtIGZzdGF0ZWRQZXJjZW50CiAgICB0YWlsUGVyY2VudCA8LSAxMDAgLSBmc3RhdGVkUGVyY2VudAogIH0KICBjb2xzID0gYyhyZXAoY29sb3IxLCB0aW1lcyA9IG5JbkhlYWQpLCByZXAoY29sb3IyLCB0aW1lcyA9IG5JblRhaWwpKQogICN5bGltPSBjKDAsIDMpLCAgCiAgYmFycGxvdChmdW5kaW5nLCBjb2wgPSBjb2xzLCBib3JkZXI9Y29scywgbWFpbiA9IE5VTEwsIAogICAgICAgICAgeHBkID0gRkFMU0UsIGxhcz0xCiAgKQogIAogICNsZWdlbmQoInJpZ2h0IiwgbGVnZW5kID0gYygiSGVhZCAoMTAlKSIsICJMb25nIFRhaWwgKDkwJSkiKSwgZmlsbCA9IGMoImdyYXk0MCIsICJncmF5IiksIGNleD0uOSkKICBsZWdlbmQoInJpZ2h0IiwgCiAgICAgICAgIGxlZ2VuZCA9IGMocGFzdGUoIkhlYWQgKCIsIGhlYWRQZXJjZW50LCAiJSkiLCBzZXAgPSAiIiksIAogICAgICAgICAgICAgICAgICAgIHBhc3RlKCJUYWlsICgiLCB0YWlsUGVyY2VudCwgIiUpIiwgc2VwID0gIiIpKSwgCiAgICAgICAgIGZpbGwgPSBjKGNvbG9yMSwgY29sb3IyKSwgY2V4PS45KQogIHRpdGxlKCJUaGUgZm9jdXMgZm9yIGxhdGVyIGFuYWx5c2lzIGlzIHNoYWRlZCBkYXJrIiwgY2V4Lm1haW49Ljk1LCBsaW5lPSAtMikgCiAgdGl0bGUoeGxhYj0iU2l6ZSBPcmRlcmVkIEdyYW50cyAtIGJhciBmb3IgZWFjaCBncmFudCIsIGxpbmU9MCkKICB0aXRsZSh5bGFiPSJEb2xsYXIgVmFsdWUgb2YgQXdhcmQgaW4gTWlsbGlvbnMiLCBsaW5lID0gMikgCn0KYGBgCgpgYGB7cn0KIyBSZWFkIGluIHRoZSBmaWxlCk15RGF0YSA8LSByZWFkLmNzdigiZGF0YS9DTlMuY3N2IiwgaGVhZGVyPVRSVUUsIGVuY29kaW5nID0gIlVURi04Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkgIyB0YWIgZGVsaW1pdGVkCgojIHB1bGwgb2ZmIHRoZSBkaXJlY3RvcnkgcGFydCBvZiB0aGUgZmlsZSBuYW1lIHRvIHVzZSBsYXRlciBmb3Igc2F2aW5nIG91dHB1dCBmaWxlcy4KI3RvcGljRGlyIDwtIHRvb2xzOjpmaWxlX3BhdGhfc2Fuc19leHQoZmlsZW5hbWUpCnRvcGljRGlyIDwtICJBd2FyZHNfQ05TIgoKZGlyLmNyZWF0ZSh0b3BpY0Rpciwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCgoKCiMgZGVjaWRlIG9uIHdoaWNoIHBhcnRzIG9mIHRoZSBjdXJ2ZSB0byBhbmFseXplCiMgVXNlciBtdXN0IGVudGVyIHRoZSAjIG9uIHRoZSBjb25zb2xlIQpsaWJyYXJ5KGRwbHlyKQoKbnNmX2Z1bmRpbmcgPC0gZGF0YV9mcmFtZShpZCA9IGFzLmNoYXJhY3RlcihNeURhdGEkQXdhcmROdW1iZXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFic3RyYWN0ID0gTXlEYXRhJEFic3RyYWN0LAogICAgICAgICAgICAgICAgICAgICAgICAgIGFtb3VudCA9IE15RGF0YSRBd2FyZGVkQW1vdW50VG9EYXRlKQojIHJlbW92ZSBodG1sIHRhZ3MgaW4gdGhlIGZvcm0gPGFueXRoaW5nPgojID8/PyBkcm9wIHJlY29yZHMgd2l0aCBlbXB0eSBhYnN0cmFjdHMKIyBjaGFuZ2VkIFBCSCAyMDE5LTAyLTI3IHRvIHB1dCBpbiBhIHNwYWNlIHdoZW4gaHRtbCBpcyByZXBsYWNlZApuc2ZfZnVuZGluZyRhYnN0cmFjdCA8LSBnc3ViKCI8W14+XSs+IiwgIiAiLCBuc2ZfZnVuZGluZyRhYnN0cmFjdCkKIyByZW1vdmUgd2hlcmUgdGhlIHdvcmQgaXMgYSBudW1iZXIKbnNmX2Z1bmRpbmckYWJzdHJhY3QgPC0gZ3N1YigiXFxkIiwgIiIsIG5zZl9mdW5kaW5nJGFic3RyYWN0KQojIGlmIHRoZXJlIGFyZSBhbnkgIndvcmRzIiB3aXRoIGltYmVkZGVkIHBlcmlvZHMgRWcgIkpvaG4uUC5Eb3VkLCByZXBsYWNlIHRoZSBwZXJvaWQgd2l0aCBhIHNwYWNlLgpuc2ZfZnVuZGluZyRhYnN0cmFjdCA8LSBnc3ViKCJcXC4iLCAiICIsIG5zZl9mdW5kaW5nJGFic3RyYWN0KQojIHJlbW92ZSAiJ3MiCm5zZl9mdW5kaW5nJGFic3RyYWN0IDwtIGdzdWIoIidzIiwgIiIsIG5zZl9mdW5kaW5nJGFic3RyYWN0KSAjIHRvIGZpeCBlZyB0cmljaG9wdGVyYTpoeWRyb3BzeWNoaWRhZSAKbnNmX2Z1bmRpbmckYWJzdHJhY3QgPC0gZ3N1YigiOiIsICIgIiwgbnNmX2Z1bmRpbmckYWJzdHJhY3QpCiNuc2ZfZnVuZGluZyRhYnN0cmFjdCA8LSBuc2ZfZnVuZGluZ1stZ3JlcCgnXlxcZCskJywgbnNmX2Z1bmRpbmckYWJzdHJhY3QpLF0KIyBDb252ZXJ0IHRoZSBzdHJpbmcgcmVwcmVlbnRhdGlvbiBvZiBudW1iZXJzIGluIHRoZSBhbW91bnQgZmllbGQgYnkgcmVtb3ZpbmcgIiQiIGFuZCAiLCIKbnNmX2Z1bmRpbmckYW1vdW50IDwtIGFzLm51bWVyaWMoZ3N1YignWyQsXScsICcnLCBuc2ZfZnVuZGluZyRhbW91bnQpKQoKIyBzb3J0IHRoZSBhYnN0cmFjdHMgYnkgdGhlIGFtb3VudCBhd2FyZGVkCiNhYnN0cmFjdF9zb3J0IDwtIG5zZl9mdW5kaW5nICU+JSAKbnNmX2Z1bmRpbmcgPC0gbnNmX2Z1bmRpbmdbb3JkZXIoLW5zZl9mdW5kaW5nJGFtb3VudCksIF0KCnN0YXRlZFBlcmNlbnQgPC0gMTAwCiMgaWYgc3RhdGVkUGVyY2VudCBpcyBub3Qgc2V0IGluIHRoZSBjb2RlIGFzayB0aGUgdXNlcgppZighZXhpc3RzKCJzdGF0ZWRQZXJjZW50Iikpe3N0YXRlZFBlcmNlbnQgPC0gcmVhZHBlcmNlbnQoKTt9CiMgaWYgaXQgaXMgbmVnYXRpdmUgdGhlbiBpdCBpcyByZWFsbHkgdGhlIHRhaWwgYW5kIHRoZSBoZWFkIGlzIHRoZSBsZW5ndGggdGhlIGFycmF5IG1pbnVzIHRoYXQgYW1vdW50CgojVGhpcyBkaXNwbGF5cyBvbiB0aGUgc2NyZWVuCkhpc3RvZ3JhbUZ1bihuc2ZfZnVuZGluZyRhbW91bnQvMTAwMDAwMCwgc3RhdGVkUGVyY2VudCwgbnJvdyhuc2ZfZnVuZGluZykpCiNUaGlzIHNhdmVzIGEgZmlsZQpwbmcocGFzdGUodG9waWNEaXIsIi9mb2N1c0hpc3RvZ3JhbS5wbmciLCBzZXA9IiIpLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQsIHVuaXRzID0gJ2luJywgcmVzID0gMzAwKQpIaXN0b2dyYW1GdW4obnNmX2Z1bmRpbmckYW1vdW50LzEwMDAwMDAsIHN0YXRlZFBlcmNlbnQsIG5yb3cobnNmX2Z1bmRpbmcpKQpkZXYub2ZmKCkKaWYgKHN0YXRlZFBlcmNlbnQgIT0gMTAwKSB7CiAgIyBPbmx5IGRvIHRoaXMgaWYgeW91IHdhbnQgdG8gb25seSBhbmFseXNlIHBhcnQgb2YgdGhlIGNvbGxlY3Rpb24KICBuc2ZfZnVuZGluZyA8LSBoZWFkKG5zZl9mdW5kaW5nLCBhcy5pbnRlZ2VyKG5yb3cobnNmX2Z1bmRpbmcpKihoZWFkcGVyY2VudC8xMDApKSkKfQpgYGAKCkZvciBhc3Ryb25vbXkgZGF0YSBtYXkgbmVlZCB0byBoaWRlIHRoZSBmZXcgYmlnIHByb2plY3RzIHRvIHNlZSB0aGUgY3VydmUuCgoKYGBge3J9CiMjICEhISAoTmVlZCBjb25zaWRlciB0aGUgc3RvcCB3b3JkKQojIyMgT25seSBydW4gdGhpcyB0byB1c2UgY3VzdG9tIHN0b3Agd29yZHMuICMjIyMKbGlicmFyeSh0aWR5dGV4dCkKI0N1c3RvbSBzdG9wIHdvcmRzCiNhZGFwdCBmb3IgQmlvISEhCm15X3N0b3Bfd29yZHMgPC0gYmluZF9yb3dzKHN0b3Bfd29yZHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2ZyYW1lKHdvcmQgPSBjKCJmb3JtYXRpb24iLCAic2NpZW5jZSIsICJzdGFycyIsICJnYWxheGllcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRhdGEiLCAic3RhciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInByb2plY3QiLCAiYXN0cm9ub215IiwgInJlc2VhcmNoIiwgInN0dWRlbnRzIiwgImdhbGF4eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInByb2dyYW0iLCAicHVibGljIiwgIm9ic2VydmF0aW9ucyIsICJzdHVkeSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyYWR1YXRlIiwgInJlc3VsdHMiLCAidGVhbSIsICJhd2FyZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRyIiwgYXMuY2hhcmFjdGVyKDE6MTIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV4aWNvbiA9IHJlcCgiY3VzdG9tIiwgMzIpKSkKYGBgCgoKCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpICMgUiBpcyBiZXR0ZXIgd2hlbiBpdCBpcyB0aWR5CmxpYnJhcnkoc3RyaW5ncikgICMgZm9yIHN0cmluZyBtYW5pcHVsYXRpb24KIyBTYXZlIGNvcHkgb2YgYWJzdHJhY3RzIGZvciBiaWdyYW0gd29yawpsaWJyYXJ5KHRpZHl0ZXh0KQpuc2ZfZnVuZGluZ19iaWdyYW0gPC0gbnNmX2Z1bmRpbmcKbnNmX2Z1bmRpbmdfc2F2ZSA8LSBuc2ZfZnVuZGluZyAjIFRoaXMgd2lsbCBiZSB1c2VkIHRvIGZpbmQgYmlncmFtcyBpbiB0aGUgZG9jdW1lbnRzLgoKbnNmX2Z1bmRpbmcgPC0gbnNmX2Z1bmRpbmcgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgYWJzdHJhY3QpICAlPiUgCiAgI2FudGlfam9pbihteV9zdG9wX3dvcmRzKSAjIEFkZCBjdXN0b20gc3RvcCB3b3Jkcy4KICBhbnRpX2pvaW4oc3RvcF93b3JkcykgIyBVc2Ugbm9ybWFsIHN0b3Agd29yZHMuCiMgcmVtb3ZlIHJvd3MgdGhhdCBhcmUgbnVsbC4KI25zZl9mdW5kaW5nIDwtIG5zZl9mdW5kaW5nWyEobnNmX2Z1bmRpbmckd29yZCA9PSBudWxsKSwgXQpuc2ZfZnVuZGluZyA8LSBuc2ZfZnVuZGluZ1shaXMubnVsbChuc2ZfZnVuZGluZyR3b3JkKSBdCm5zZl9mdW5kaW5nICU+JSBmaWx0ZXIobnNmX2Z1bmRpbmckd29yZCAhPSAibnVsbCIgKQpuc2ZfZnVuZGluZ19iaWdyYW0gPC0gbnNmX2Z1bmRpbmdfYmlncmFtICU+JSAKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgYWJzdHJhY3QsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgCiAgI2FudGlfam9pbihteV9zdG9wX3dvcmRzKSAjIEFkZCBjdXN0b20gc3RvcCB3b3Jkcy4KICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnkgPSBjKCJiaWdyYW0iID0gIndvcmQiKSkgIyBVc2Ugbm9ybWFsIHN0b3Agd29yZHMuCmxlbW1hX3VuaXF1ZSA8LSBuc2ZfZnVuZGluZyAlPiUKICBzZWxlY3Qod29yZCkgJT4lCiAgbXV0YXRlKHdvcmRfY2xlYW4gPSBzdHJfcmVwbGFjZV9hbGwod29yZCwiXHUyMDE5c3wncyIsIiIpKSAlPiUKIyAgbXV0YXRlKHdvcmRfY2xlYW4gPSBpZmVsc2Uoc3RyX2RldGVjdCh3b3JkX2NsZWFuLCJbXls6YWxwaGE6XV0iKSxOQSx3b3JkX2NsZWFuKSkgJT4lCiAgIyB0aGlzIDphbHBoYTogY2F1c2VzIHByb2JsZW1zIHdpdGggVVRGLTgKICMgbXV0YXRlKHdvcmRfY2xlYW4gPSBpZmVsc2Uoc3RyX2RldGVjdCh3b3JkX2NsZWFuLCJbXls6YWxwaGE6XV0iKSxOQSx3b3JkX2NsZWFuKSkgJT4lCiAgZmlsdGVyKCFkdXBsaWNhdGVkKHdvcmRfY2xlYW4pKSAlPiUKICBmaWx0ZXIoIWlzLm5hKHdvcmRfY2xlYW4pKSAlPiUKICBhcnJhbmdlKHdvcmQpCmBgYAoKCmBgYHtyfQojbG9hZCBwYWNrYWdlcyBrb1J1cHVzIGFuZCBzeWxseQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHN5bGx5KQpsaWJyYXJ5KGtvUnB1cykKI2luc3RhbGwua29ScHVzLmxhbmcoImVuIikKbGlicmFyeShrb1JwdXMubGFuZy5lbikKIyBzZXR1cCBmb3IgbGVtbWFuaXphdGlvbgojIE91dHNpZGUgb2YgUiBJbnN0YWxsIHRyZWUtdGFnZ2VyIGZyb20gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2tvUnB1cy92aWduZXR0ZXMva29ScHVzX3ZpZ25ldHRlLmh0bWwKIyBodHRwOi8vd3d3LmNpcy51bmktbXVlbmNoZW4uZGUvfnNjaG1pZC90b29scy9UcmVlVGFnZ2VyLyNwYXJmaWxlcwojIHRvIGluc3RhbGwgdHJlZS10YWdnZXIgaW4gb3JkZXIgdG8gcnVuIGtvUnB1cyBpbiBSCnN5c3RlbS50aW1lKAogIGxlbW1hX3RhZ2dlZCA8LSB0cmVldGFnKGxlbW1hX3VuaXF1ZSR3b3JkX2NsZWFuLCB0cmVldGFnZ2VyPSJtYW51YWwiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9Im9iaiIsIFRULnRrbno9RkFMU0UgLCBsYW5nPSJlbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgVFQub3B0aW9ucz1saXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgIHBhdGg9IkFwcGxpY2F0aW9ucy9UcmVlVGFnZ2VyIiwgcHJlc2V0PSJlbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkZWJ1Zz1UUlVFCiAgICAgICAgICAgICAgICAgICAgICAgICAgKQopCiMgTWFrZSBzdXJlIGFsbCBwYWNrYWdlcyBpbmNsdWRpbmcgdGlkeXZlcnNlIGlzIGxvYWRlZCBvciB5b3UgbWF5IGdldCBhbiBlcnJvciB0aGF0IHRoZSBwcmVzZXQgaXMgbm90IHJlY29nbml6ZWQuIEluIHBhcnRpY3VsYXIgY2hlY2sga29ScHVzLmxhbmcuZW4KCiNsZW1tYV90YWdnZWRfdGJsIDwtIHRibF9kZihsZW1tYV90YWdnZWRAVFQucmVzKQojbGVtbWFfdGFnZ2VkX3RibCA8LSB0aWJibGUobGVtbWFfdGFnZ2VkQFRULnJlcykKbGVtbWFfdGFnZ2VkX3RibCA8LSBhc190aWJibGUobGVtbWFfdGFnZ2VkKQojIHJlcGxhY2UgdW5rbm93bnMgd2l0aCB0aGUgb2xkIHJhdyB3b3JkCiNsZW1tYV91bmlxdWUgPC0gbGVtbWFfdW5pcXVlICU+JSAKIyAgbGVmdF9qb2luKGxlbW1hX3RhZ2dlZF90YmwgJT4lCiMgICAgICAgICAgICAgIGZpbHRlcihsZW1tYSAhPSAiPHVua25vd24+IikgJT4lCiMgICAgICAgICAgICAgIHNlbGVjdCh0b2tlbiwgbGVtbWEsIHdjbGFzcyksIGJ5ID0gYygid29yZF9jbGVhbiIgPSAidG9rZW4iKQojICAgICAgICAgICAgKSAlPiUKIyAgYXJyYW5nZSh3b3JkKQojIHJlcGxhY2Ugd2NsYXNzIHcvIHRhZwpsZW1tYV91bmlxdWUgPC0gbGVtbWFfdW5pcXVlICU+JSAKICBsZWZ0X2pvaW4obGVtbWFfdGFnZ2VkX3RibCAlPiUKICAgICAgICAgICAgICBmaWx0ZXIobGVtbWEgIT0gIjx1bmtub3duPiIpICU+JQogICAgICAgICAgICAgIHNlbGVjdCh0b2tlbiwgbGVtbWEsIHRhZyksIGJ5ID0gYygid29yZF9jbGVhbiIgPSAidG9rZW4iKQogICAgICAgICAgICApICU+JQogIGFycmFuZ2Uod29yZCkKCiMgUmVwbGFjZSBhbGwgTkEgd2l0aCB0aGUgd29yZCBpbiB3b3JkX2NsZWFuCmxlbW1hX3VuaXF1ZSA9IHdpdGhpbihsZW1tYV91bmlxdWUsIHsKICBsZW1tYSA9IGlmZWxzZShpcy5uYShsZW1tYSksIHdvcmRfY2xlYW4sIGxlbW1hKSAgfSApCgpgYGAKCmBgYHtyfQojcmVwbGFjZSBhbGwgb2YgdGhlIG9yaWdpbmFsIHdvcmRzIGluIHRoZSBuc2ZfZnVuZGluZyB3aXRoIGxlbW1hcwojIFRoaXMgYWxzbyBuZWVkcyB0byBiZSBkb25lIGZvciB0aGUgYmlncmFtcyB3aGVyZSB0aGUgc3Vic3RhdHV0aW9uIGhhcHBlbnMgZm9yIGVhY2ggb2YgdGhlIHR3byBjb2x1bW5zIGluIHRoZSBiaWdyYW0uID8/PwpsZW1tYV9uZXcgPC0gbGVtbWFfdW5pcXVlCiNsZW1tYV9uZXcgPC0gbGVmdF9qb2luKG5zZl9mdW5kaW5nLGxlbW1hX25ldywgYnkgPSAid29yZCIpCmxlbW1hX25ldyA8LSBsZWZ0X2pvaW4obnNmX2Z1bmRpbmcsbGVtbWFfbmV3LCBieSA9ICJ3b3JkIikgJT4lCiAgbXV0YXRlKHdvcmQgPSBpZmVsc2UoaXMubmEobGVtbWEpLCB3b3JkX2NsZWFuLCBsZW1tYSkpICMgJT4lCmxlbW1hX25ldyA8LSBzdWJzZXQobGVtbWFfbmV3LCBzZWxlY3QgPSBjKGlkLCB3b3JkKSkKbGVtbWFfbmV3W2lzLm5hKGxlbW1hX25ldyR3b3JkKSxdCgpgYGAKVGhlcmUgc2hvdWxkIGJlIG5vIE5BIHdvcmRzISEhIGlmIHNvIHRoZXJlIGlzIGEgcHJvYmxlbSB3aXRoIHRoZSBpbnB1dC4uLiBVVEYgY29udmVyc2lvbiBwcm9ibGVtLgpgYGB7cn0KbGlicmFyeSh0bSkKI215Q29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UobnNmX2Z1bmRpbmckd29yZCkpIApteUNvcnB1cyA8LSBDb3JwdXMoVmVjdG9yU291cmNlKGxlbW1hX25ldyR3b3JkKSkgCgpgYGAKCmBgYHtyfQojY3JlYXRlIHRoZSB0b1NwYWNlIGNvbnRlbnQgdHJhbnNmb3JtZXIKdG9TcGFjZSA8LSBjb250ZW50X3RyYW5zZm9ybWVyKGZ1bmN0aW9uKHgsIHBhdHRlcm4pIHtyZXR1cm4gKGdzdWIocGF0dGVybiwgIiAiLCB4KSl9KQoKI1N0cmlwIGRpZ2l0cyAoc3RkIHRyYW5zZm9ybWF0aW9uLCBzbyBubyBuZWVkIGZvciBjb250ZW50X3RyYW5zZm9ybWVyKQpteUNvcnB1cyA8LSB0bV9tYXAobXlDb3JwdXMsIHJlbW92ZU51bWJlcnMpCgpkdG0gPC0gRG9jdW1lbnRUZXJtTWF0cml4KG15Q29ycHVzKQoKZnJlcSA8LSBjb2xTdW1zKGFzLm1hdHJpeChkdG0pKQoKI2NyZWF0ZSBzb3J0IG9yZGVyIChkZXNjZW5kaW5nKQpvcmQgPC0gb3JkZXIoZnJlcSxkZWNyZWFzaW5nPVRSVUUpCgojaW5zcGVjdCBtb3N0IGZyZXF1ZW50bHkgb2NjdXJyaW5nIHRlcm1zCmZyZXFbaGVhZChvcmQpXQojaW5zcGVjdCBsZWFzdCBmcmVxdWVudGx5IG9jY3VycmluZyB0ZXJtcwpmcmVxW3RhaWwob3JkKV0KYGBgCgoKYGBge3J9CiMgUGxvdCB0aGUgb2NjdXJlbmNlIGZyZXF1ZW5jaWVzCndmPWRhdGEuZnJhbWUodGVybT1uYW1lcyhmcmVxKSxvY2N1cnJlbmNlcz1mcmVxKQpsaWJyYXJ5KGdncGxvdDIpCnAgPC0gZ2dwbG90KHN1YnNldCh3ZiwgZnJlcT4yMCksIGFlcyh0ZXJtLCBvY2N1cnJlbmNlcykpCnAgPC0gcCArIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikKcCA8LSBwICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkKcApgYGAKCmBgYHtyfQojd29yZGNsb3VkCmxpYnJhcnkod29yZGNsb3VkKQojc2V0dGluZyB0aGUgc2FtZSBzZWVkIGVhY2ggdGltZSBlbnN1cmVzIGNvbnNpc3RlbnQgbG9vayBhY3Jvc3MgY2xvdWRzCnNldC5zZWVkKDQyKQpwbmcocGFzdGUodG9waWNEaXIsIi9GcmVxV29yZENsb3VkLnBuZyIsIHNlcD0iIiksIHdpZHRoPTEyODAsaGVpZ2h0PTgwMCkKI2xpbWl0IHdvcmRzIGJ5IHNwZWNpZnlpbmcgbWluIGZyZXF1ZW5jeQp3b3JkY2xvdWQobmFtZXMoZnJlcSksZnJlcSwgbWF4LndvcmRzPTEwMCwgc2NhbGU9Yyg0LC4yKSwgY29sb3JzPWJyZXdlci5wYWwoNiwiRGFyazIiKSkKd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCiNTZWNvbmQgY2FsbCBwdXRzIHRoZSBjbG91ZCBpbWFnZSBvbiB0aGUgc2NyZWVuCndvcmRjbG91ZChuYW1lcyhmcmVxKSwgZnJlcSwgbWF4LndvcmRzPTEwMCwgc2NhbGU9Yyg0LC4yKSwgY29sb3JzPWJyZXdlci5wYWwoNiwiRGFyazIiKSkKCmBgYAoKYGBge3J9CiMjVG9waWMgTW9kZWxpbmcgdXNpbmcgbGVtbWFzCgojd29yZF9jb3VudHMgPC0gbnNmX2Z1bmRpbmcgJT4lCndvcmRfY291bnRzIDwtIGxlbW1hX25ldyAlPiUKICAjYW50aV9qb2luKG15X3N0b3Bfd29yZHMpICU+JQogIGNvdW50KGlkLCB3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgdW5ncm91cCgpCgp3b3JkX2NvdW50cwpgYGAKCmBgYHtyfQojUmVtb3ZlIG51bWJlcnMKI3dvcmRfY291bnRzPC13b3JkX2NvdW50c1stZ3JlcCgiXFxiXFxkK1xcYiIsIHdvcmRfY291bnRzJHdvcmQpLF0KCmRlc2NfZHRtIDwtIHdvcmRfY291bnRzICU+JQogIGNhc3RfZHRtKGlkLCB3b3JkLCBuKQoKZGVzY19kdG0KYGBgCgpgYGB7cn0KI0NhbGN1bGF0ZSB0Zl9pZGYKI3dvcmRfY291bnRzIDwtIHdvcmRfY291bnRzICU+JQogI2JpbmRfdGZfaWRmKGlkLCB3b3JkLCBuKQojd29yZF9jb3VudHMKYGBgCgpgYGB7cn0KI0NvbnRpbnVlIHRmX2lkZgojd29yZF9jb3VudHMgJT4lCiAgI2FycmFuZ2UodGZfaWRmKQpgYGAKCgpgYGB7cn0KI0NhbGN1bGF0ZSB0Zi1pZGYKI2Rlc2NfdGZfaWRmIDwtIG5zZl9mdW5kaW5nICU+JSAKZGVzY190Zl9pZGYgPC0gbGVtbWFfbmV3ICU+JSAKICBjb3VudChpZCwgd29yZCwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBiaW5kX3RmX2lkZih3b3JkLCBpZCwgbikKYGBgCgoKI05ldyBkb2N1bWVudCB0ZXJtIG1hdHJpeCBmb3IgdG9waWMgbW9kZWxpbmcgd2l0aCBjdXN0b20gc3RvcCB3b3JkcwojPz8/IHJhdGhlciB0aGFuIGxvdyBpZGYgdXNlIGxvdyB0Zl9pZGYgLSBtYXkgYmUgcmVwbGFjZWQgd2l0aCBhIHRyaW0gb2YgdGhlIHdvcmQgbGlzdCBieSBmdC1pZGYKbG93X2lkZiA8LSBzdWJzZXQoZGVzY190Zl9pZGYsIGlkZj4wLjM4ICYgaWRmPDIpCiN0b3BpY0ZpbGVOYW1lIDwtIGdzdWIoIi5jc3YiLCJsb3dfaWRmLmNzdiIsIGZpbGVuYW1lKQp3cml0ZS5jc3YobG93X2lkZixwYXN0ZSh0b3BpY0RpciwiL2xvd19pZGYuY3N2Iiwgc2VwPSIiKSkKCgoKCmBgYHtyfQojV2hhdCBhcmUgaGlnaGVzdCB0Zi1pZGYgd29yZHM/ICgqKQpkZXNjX3RmX2lkZiAlPiUgCiAgYXJyYW5nZSgtdGZfaWRmKQpgYGAKCgpgYGB7cn0KI1doYXQgYXJlIGxvd2VzdCBpZGYgd29yZHM/CmRlc2NfdGZfaWRmICU+JSAKICBhcnJhbmdlKGlkZikKYGBgCgoKCgoKCgoKYGBge3J9CmZyZXEgPC0gY29sU3Vtcyhhcy5tYXRyaXgoZGVzY19kdG0pKQoKI2NyZWF0ZSBzb3J0IG9yZGVyIChkZXNjZW5kaW5nKQpvcmQgPC0gb3JkZXIoZnJlcSxkZWNyZWFzaW5nPVRSVUUpCgojaW5zcGVjdCBtb3N0IGZyZXF1ZW50bHkgb2NjdXJyaW5nIHRlcm1zCmZyZXFbaGVhZChvcmQpXQpgYGAKCmBgYHtyfQojaW5zcGVjdCBsZWFzdCBmcmVxdWVudGx5IG9jY3VycmluZyB0ZXJtcwpmcmVxW3RhaWwob3JkKV0KYGBgCgpgYGB7cn0KI0lnbm9yZSB0aGlzIG1heWJlPz8/IGRvIG5vdCB1c2UuCiN3Zj1kYXRhLmZyYW1lKHRlcm09bmFtZXMoZnJlcSksb2NjdXJyZW5jZXM9ZnJlcSkKI2xpYnJhcnkoZ2dwbG90MikKI3RvcGljRmlsZU5hbWUgPC0gZ3N1YigiLmNzdiIsInRmLWlkZmhpc3RvZ3JhbS5wbmciLCBmaWxlbmFtZSkKI3AgPC0gZ2dwbG90KHN1YnNldCh3ZiwgZnJlcT4yNTApLCBhZXModGVybSwgb2NjdXJyZW5jZXMpKQojcCA8LSBwICsgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQojcCA8LSBwICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkKI3AKI2dnc2F2ZSh0b3BpY0ZpbGVOYW1lLCBkZXZpY2UgPSAicG5nIikKYGBgCgpgYGB7cn0KCiN3b3JkY2xvdWQKbGlicmFyeSh3b3JkY2xvdWQpCiNzZXR0aW5nIHRoZSBzYW1lIHNlZWQgZWFjaCB0aW1lIGVuc3VyZXMgY29uc2lzdGVudCBsb29rIGFjcm9zcyBjbG91ZHMKc2V0LnNlZWQoNDIpCnBuZyhwYXN0ZSh0b3BpY0RpciwiL1RGLUlERldvcmRDbG91ZC5wbmciLCBzZXA9IiIpLCB3aWR0aD0xMjgwLGhlaWdodD04MDApCiNsaW1pdCB3b3JkcyBieSBzcGVjaWZ5aW5nIG1pbiBmcmVxdWVuY3kKI3dvcmRjbG91ZChuYW1lcyhmcmVxKSxmcmVxLCBtaW4uZnJlcT04MCxjb2xvcnM9YnJld2VyLnBhbCg2LCJEYXJrMiIpKQp3b3JkY2xvdWQobmFtZXMoZnJlcSksZnJlcSwgbWluLmZyZXE9MjAsc2NhbGU9Yyg0LC4yKSwgY29sb3JzPWJyZXdlci5wYWwoNiwiRGFyazIiKSkKd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCndvcmRjbG91ZChuYW1lcyhmcmVxKSxmcmVxLCBtaW4uZnJlcT0yMCxzY2FsZT1jKDQsLjIpLCBjb2xvcnM9YnJld2VyLnBhbCg2LCJEYXJrMiIpKQoKYGBgCgpodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbGRhdHVuaW5nL3ZpZ25ldHRlcy90b3BpY3MuaHRtbApTZWxlY3QgbnVtYmVyIG9mIHRvcGljcyBmb3IgTERBIG1vZGVsCk11cnppbnRjZXYgTmlraXRhCjIwMTYtMTAtMjQKClBhY2thZ2UgY2FuIGJlIGluc3RhbGxlZCBmcm9tIENSQU4KUGFja2FnZSBsZGF0dW5pbmcgcmVhbGl6ZXMgNCBtZXRyaWNzIHRvIHNlbGVjdCBwZXJmZWN0IG51bWJlciBvZiB0b3BpY3MgZm9yIExEQSBtb2RlbC4KYGBge3IgZmlnLndpZHRoPTExfQojTERBIFR1bmluZzogUGljayB0aGUgTnVtYmVyIG9mIHRvcGljcwojaW5zdGFsbC5wYWNrYWdlcygibGRhdHVuaW5nIikKIyBDaGFuZ2VkIG1ldGhvZCBpbiB0aGUgZXhtYXBsZSBmcm9tICJHaWJicyIiIHRvICJWRU0iIHRvIG1hdGhjIHRoZSBOYXNhIEV4YW1wbGUKbGlicmFyeSgibGRhdHVuaW5nIikKbGlicmFyeSh0b3BpY21vZGVscykKc3lzdGVtLnRpbWUoCiAgcmVzdWx0IDwtIEZpbmRUb3BpY3NOdW1iZXIoCiAgZGVzY19kdG0sCiAgdG9waWNzID0gc2VxKGZyb20gPSAxMCwgdG8gPSA1MCwgYnkgPSAxKSwKICBtZXRyaWNzID0gYygiR3JpZmZpdGhzMjAwNCIsICJDYW9KdWFuMjAwOSIsICJBcnVuMjAxMCIsICJEZXZlYXVkMjAxNCIpLAogIG1ldGhvZCA9ICJHaWJicyIsCiAgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEyMzQpLAogIG1jLmNvcmVzID0gNEwsCiAgdmVyYm9zZSA9IFRSVUUKKSkKcG5nKHBhc3RlKHRvcGljRGlyLCIvRmluZFRvcGljTnVtYmVyLnBuZyIsIHNlcD0iIiksIHdpZHRoPTEyODAsaGVpZ2h0PTgwMCkKRmluZFRvcGljc051bWJlcl9wbG90KHJlc3VsdCkKd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCkZpbmRUb3BpY3NOdW1iZXJfcGxvdChyZXN1bHQpCmBgYApHaWJicyBtZXRob2QgY29uc2Vuc3VzIHNob3dzIGEgcGVhayBvZiAyMyBmb3IgdGhlIERFQiBkYXRhCkdpYmJzIG1ldGhvZCBjb25zZW5zdXMgc2hvd3MgYSBwZWFrIG9mIDMwIGZvciB0aGUgQVNUIGRhdGEKCmBgYHtyfQpoYXJtb25pY01lYW4gPC0gZnVuY3Rpb24obG9nTGlrZWxpaG9vZHMsIHByZWNpc2lvbiA9IDIwMDBMKSB7CiAgbGxNZWQgPC0gbWVkaWFuKGxvZ0xpa2VsaWhvb2RzKQogIGFzLmRvdWJsZShsbE1lZCAtIGxvZyhtZWFuKGV4cCgtbXBmcihsb2dMaWtlbGlob29kcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlYyA9IHByZWNpc2lvbikgKyBsbE1lZCkpKSkKfQpgYGAKCmBgYHtyfQpzZXFrIDwtIHNlcSgyLCAxMDAsIDEpCmJ1cm5pbiA8LSAxMDAwCml0ZXIgPC0gMTAwMAprZWVwIDwtIDUwCnN5c3RlbS50aW1lKGZpdHRlZF9tYW55IDwtIGxhcHBseShzZXFrLCBmdW5jdGlvbihrKSB0b3BpY21vZGVsczo6TERBKGRlc2NfZHRtLCBrID0gaywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiR2liYnMiLGNvbnRyb2wgPSBsaXN0KGJ1cm5pbiA9IGJ1cm5pbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXIgPSBpdGVyLCBrZWVwID0ga2VlcCkgKSkpCnJvdW5kdG8xMCA8LSBmdW5jdGlvbih4KSBjZWlsaW5nKG1heCh4KS8xMCkqMTAKYGBgCgpgYGB7cn0KIyBleHRyYWN0IGxvZ2xpa3MgZnJvbSBlYWNoIHRvcGljCmxpYnJhcnkoIlJtcGZyIikKbG9nTGlrc19tYW55IDwtIGxhcHBseShmaXR0ZWRfbWFueSwgZnVuY3Rpb24oTCkgIExAbG9nTGlrc1stYygxOihidXJuaW4va2VlcCkpXSkKCiMgY29tcHV0ZSBoYXJtb25pYyBtZWFucwpobV9tYW55IDwtIHNhcHBseShsb2dMaWtzX21hbnksIGZ1bmN0aW9uKGgpIGhhcm1vbmljTWVhbihoKSkKCiNOb3cgdGhhdCBpcyBoYXZlIGNhbGN1bGF0ZWQgdGhlIGhhcm1vbmljIG1lYW5zIG9mIHRoZSBtb2RlbHMgZm9yIDIgLSAxMDAgdG9waWNzLCB5b3UgY2FuIHBsb3QgdGhlIHJlc3VsdHMgdXNpbmcgZ2dwbG90MiBwYWNrYWdlLgoKcmVxdWlyZShzY2FsZXMpCmxkYXBsb3QgPC0gZ2dwbG90KGRhdGEuZnJhbWUoc2VxaywgaG1fbWFueSksIGFlcyh4PXNlcWssIHk9aG1fbWFueSkpICsgZ2VvbV9wYXRoKGx3ZD0xLjUpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseT0gTlVMTCksCiAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfdGV4dCh2anVzdD0xLCBzaXplPTE2KSwKICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHZqdXN0PS0uNSwgc2l6ZT0xNiksCiAgICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICAgICAgICBwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTIwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJ051bWJlciBvZiBUb3BpY3MnKSArCiAgeWxhYignSGFybW9uaWMgTWVhbicpICsKICBnZ3Bsb3QyOjphbm5vdGF0ZSgidGV4dCIsIHggPSAyNSwgeSA9IHJvdW5kdG8xMChtYXgoaG1fbWFueSkrMTAwMDApLCAjIGNhbGN1bGF0ZSB0aGUgbWF4IHJhdGhlciB0aGFuIGhhcmQgY29kZSA/Pz8KICAgICAgICAgICBsYWJlbCA9IHBhc3RlKCJUaGUgb3B0aW1hbCBudW1iZXIgb2YgdG9waWNzIGlzIiwgc2Vxa1t3aGljaC5tYXgoaG1fbWFueSldKSkgKwogIGdndGl0bGUoZXhwcmVzc2lvbihhdG9wKCJMREEgQW5hbHlzaXMgb2YgTlNGIFByb2dyYW0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhdG9wKGl0YWxpYygiSGFybW9uaWMgTWVhbiIpLCAiIikpKSkKIApwbmcocGFzdGUodG9waWNEaXIsIi9MREFIYW1vbmljTWVhbi5wbmciLCBzZXA9IiIpLCB3aWR0aD0xMjgwLGhlaWdodD04MDApCmxkYXBsb3QKd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCmxkYXBsb3QKYGBgCgpgYGB7cn0KI0hhcm1vbmljIG1lYW4gYWJvdmUgaGFzIG9wdGltdW0gb2YgMjAgZm9yIGNvbWJpbmVkIGNvbGxhYm9yYXRpb25zIGluIGFzdHJvbm9teS4gUHJpb3IgYW5hbHlzaXMgdXNlZCAyMiBidXQgdGhlIHBlYWsgaXMgYWJvdXQgdGhlIHNhbWUuCiNJbnNwaXJlZCBieTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL25hc2EuaHRtbCN0b3BpYy1tb2RlbGluZwpsaWJyYXJ5KHRvcGljbW9kZWxzKQoKI0hhcm1vbmljIG1lYW4gd2FzIDM0IHRvcGljcyBidXQgMjkgaXMgYWxtb3N0IHRoZSBzYW1lOyAKIyBbMjNdIC00MDg2ODAuMSAtNDA3OTEwLjEgLTQwODA3MS45IC00MDc0MjIuNyAtNDA3MTczLjkgLTQwNjM0Mi4wIC00MDc1ODMuMyAtNDA3NjE0LjEgLTQwNzc4Mi4yIC00MDgxMTUuMyAtNDA1NTA3LjYKIyBiZSBhd2FyZSB0aGF0IHJ1bm5pbmcgdGhpcyBtb2RlbCBpcyB0aW1lIGludGVuc2l2ZQpOdW1Ub3BpY3MgPC0gMTgKZGVzY19sZGEgPC0gTERBKGRlc2NfZHRtLCBtZXRob2QgPSAiR2liYnMiLCBrID0gTnVtVG9waWNzLCBjb250cm9sID0gbGlzdChzZWVkID0gMTIzNCkpCmRlc2NfbGRhCiMgU2luY2UgaXQgaXMgdGltZSBjb25zdW1pbmcgdG8gY3JlYXRlIHRoZSBkZXNjX2xkYSBmaWxlIHdlIHNhdmUgaXQgaGVyZSBmb3IgYmFja3VwLiAKc2F2ZVJEUyhkZXNjX2xkYSwgZmlsZSA9IHBhc3RlKHRvcGljRGlyLCIvZGVzY19sZGEucmRzIiwgc2VwID0gIiIpKQojIHRvIGdldCBpdCBiYWNrIGRlc2NfbGRhIDwtIGxvYWQocGFzdGUodG9waWNEaXIsIi9kZXNjX2xkYS5SRGF0YSIpKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl0ZXh0KQp0aWR5X2xkYSA8LSB0aWR5KGRlc2NfbGRhKQoKdGlkeV9sZGEKYGBgCgpgYGB7cn0KI0V4YW1pbmUgdG9wIDEwIHRlcm1zIGZvciBlYWNoIHRvcGljCnRvcF90ZXJtcyA8LSB0aWR5X2xkYSAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgdG9wX24oMTAsIGJldGEpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCnRvcF90ZXJtcwpgYGAKCmBgYHtyfQoKI2h0dHBzOi8vdG00c3MuZ2l0aHViLmlvL2RvY3MvVHV0b3JpYWxfNl9Ub3BpY19Nb2RlbHMuaHRtbAoKdG9wMTB0ZXJtc1BlclRvcGljIDwtIHRlcm1zKGRlc2NfbGRhLCAxMCkKdG9waWNOYW1lcyA8LSBhcHBseSh0b3AxMHRlcm1zUGVyVG9waWMsIDIsIHBhc3RlLCBjb2xsYXBzZT0iICIpCgojIGhhdmUgYSBsb29rIGF0IHNvbWUgb2YgdGhlIHJlc3VsdHMgKHBvc3RlcmlvciBkaXN0cmlidXRpb25zKQp0bVJlc3VsdCA8LSBwb3N0ZXJpb3IoZGVzY19sZGEpCmBgYAoKYGBge3J9CmxpYnJhcnkod29yZGNsb3VkKQojIHZpc3VhbGl6ZSB0b3BpY3MgYXMgd29yZCBjbG91ZApmb3IgKFRvcGljTnVtIGluIDE6TnVtVG9waWNzKXsKICBzZXQuc2VlZCg0MikKICB0b3BpY1RvVml6IDwtIFRvcGljTnVtICMgY2hhbmdlIGZvciB5b3VyIG93biB0b3BpYyBvZiBpbnRlcmVzdAogICN0b3BpY1RvVml6IDwtIDEgIyBjaGFuZ2UgZm9yIHlvdXIgb3duIHRvcGljIG9mIGludGVyZXN0CiAgI3RvcGljVG9WaXogPC0gZ3JlcCgndGVsZXNjb3BlJywgdG9waWNOYW1lcylbMV0gIyBPciBzZWxlY3QgYSB0b3BpYyBieSBhIHRlcm0gY29udGFpbmVkIGluIGl0cyBuYW1lCiAgIyBzZWxlY3QgdG8gMTAwIG1vc3QgcHJvYmFibGUgdGVybXMgZnJvbSB0aGUgdG9waWMgYnkgc29ydGluZyB0aGUgdGVybS10b3BpYy1wcm9iYWJpbGl0eSB2ZWN0b3IgaW4gZGVjcmVhc2luZyBvcmRlcgogIHRvcDEwMHRlcm1zIDwtCiAgc29ydCh0bVJlc3VsdCR0ZXJtc1t0b3BpY1RvVml6LCBdLCBkZWNyZWFzaW5nID0gVFJVRSlbMToxMDBdCiAgd29yZHMgPC0gbmFtZXModG9wMTAwdGVybXMpCiAgIyBleHRyYWN0IHRoZSBwcm9iYWJpbGl0ZXMgb2YgZWFjaCBvZiB0aGUgMTAwIHRlcm1zCiAgcHJvYmFiaWxpdGllcyA8LQogIHNvcnQodG1SZXN1bHQkdGVybXNbdG9waWNUb1ZpeiwgXSwgZGVjcmVhc2luZyA9IFRSVUUpWzE6MTAwXQogICMgdmlzdWFsaXplIHRoZSB0ZXJtcyBhcyB3b3JkY2xvdWQKICBteWNvbG9ycyA8LSBicmV3ZXIucGFsKDgsICJEYXJrMiIpCiAgI3RvcGljRmlsZU5hbWUgPC0gZ3N1YigiLmNzdiIsIHBhc3RlKCJUb3BpYyIsIHRvU3RyaW5nKFRvcGljTnVtKSwgIldvcmRDbG91ZC5wbmciLCBzZXAgPSAiIiksIGZpbGVuYW1lKSAKICBwbmcocGFzdGUodG9waWNEaXIsIi9Xb3JkQ2xvdWQucG5nIiwgc2VwPSIiKSwgd2lkdGggPSAxMjgwLCBoZWlnaHQgPSA4MDApCiAgd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCiAgd29yZGNsb3VkKAogIHdvcmRzLAogIHByb2JhYmlsaXRpZXMsCiAgc2NhbGUgPSBjKDQsIC4yKSwKICByYW5kb20ub3JkZXIgPSBGQUxTRSwKICBjb2xvciA9IG15Y29sb3JzCiAgKQogIGRldi5vZmYoKQogIHdvcmRjbG91ZCgKICB3b3JkcywKICBwcm9iYWJpbGl0aWVzLAogIHNjYWxlID0gYyg0LCAuMiksCiAgcmFuZG9tLm9yZGVyID0gRkFMU0UsCiAgY29sb3IgPSBteWNvbG9ycwogICkKfQpgYGAKCgoKCmBgYHtyIGZpZy5oZWlnaHQ9MzB9CmxpYnJhcnkoZ2dwbG90MikKcG5nKHBhc3RlKHRvcGljRGlyLCIvVGVybXNMREFIaXN0b2dyYW0ucG5nIiwgc2VwPSIiKSwgd2lkdGggPSAxMjgwLCBoZWlnaHQgPSAxNjAwKQp0b3BfdGVybXMgJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyKHRlcm0sIGJldGEpKSAlPiUKICBncm91cF9ieSh0b3BpYywgdGVybSkgJT4lICAgIAogIGFycmFuZ2UoZGVzYyhiZXRhKSkgJT4lICAKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHRlcm0gPSBmYWN0b3IocGFzdGUodGVybSwgdG9waWMsIHNlcCA9ICJfXyIpLCAKICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSByZXYocGFzdGUodGVybSwgdG9waWMsIHNlcCA9ICJfXyIpKSkpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGFzLmZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBnc3ViKCJfXy4rJCIsICIiLCB4KSkgKwogIGxhYnModGl0bGUgPSAiVG9wIHRlcm1zIGluIGVhY2ggTERBIHRvcGljIiwKICAgICAgIHggPSBOVUxMLCB5ID0gZXhwcmVzc2lvbihiZXRhKSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikKI3RvcGljRmlsZU5hbWUgPC0gZ3N1YigiLmNzdiIsIlRlcm1zTERBSGlzdG9ncmFtLnBuZyIsIGZpbGVuYW1lKQojZ2dzYXZlKHRvcGljRmlsZU5hbWUsIGhlaWdodCA9IDE0LCBkZXZpY2UgPSAicG5nIikKI3BuZyhwYXN0ZSh0b3BpY0RpciwiL1Rlcm1zTERBSGlzdG9ncmFtLnBuZyIsIHNlcD0iIiksIHdpZHRoID0gMTI4MCwgaGVpZ2h0ID0gMTYwMCkKd2hpbGUgKCFpcy5udWxsKGRldi5saXN0KCkpKSAgZGV2Lm9mZigpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0zMH0KIyBDYWxjdWxhdGUgdGhlIHRvcCB0ZXJtcyBpbiBlYWNoIHRvcGljCnRvcF90ZXJtcyAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXIodGVybSwgYmV0YSkpICU+JQogIGdyb3VwX2J5KHRvcGljLCB0ZXJtKSAlPiUgICAgCiAgYXJyYW5nZShkZXNjKGJldGEpKSAlPiUgIAogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUodGVybSA9IGZhY3RvcihwYXN0ZSh0ZXJtLCB0b3BpYywgc2VwID0gIl9fIiksIAogICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IHJldihwYXN0ZSh0ZXJtLCB0b3BpYywgc2VwID0gIl9fIikpKSkgJT4lCiAgZ2dwbG90KGFlcyh0ZXJtLCBiZXRhLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIGdzdWIoIl9fLiskIiwgIiIsIHgpKSArCiAgbGFicyh0aXRsZSA9ICJUb3AgdGVybXMgaW4gZWFjaCBMREEgdG9waWMiLAogICAgICAgeCA9IE5VTEwsIHkgPSBleHByZXNzaW9uKGJldGEpKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBuY29sID0gMywgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyfQojVG9wIDEwIHRlcm1zIGluIGVhY2ggbW9kZWwKCnRlcm1zKGRlc2NfbGRhLCAxMCkKYGBgCgpgYGB7cn0KIyNFeGFtaW5lIHdoaWNoIHRvcGljcyBhcmUgYXNzb2NpYXRlZCB3aXRoIHdoaWNoIGRvY3VtZW50cwoKbGRhX2dhbW1hIDwtIHRpZHkoZGVzY19sZGEsIG1hdHJpeCA9ICJnYW1tYSIpCgpsZGFfZ2FtbWEKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTE1LCBmaWcud2lkdGg9MTB9CgpsaWJyYXJ5KGRhdGEudGFibGUpCiMgQ2hhbmdlIHRoZSBpbmRleCB0byBiZSBhIGNvbHVtbi4gbnNmX2Z1bmRpbmdfc2F2ZSBpcyBpbiBmdW5kaW5nIG9yZGVyLgpzZXREVChuc2ZfZnVuZGluZ19zYXZlLCBrZWVwLnJvd25hbWVzID0gVFJVRSlbXQojQ2hhbmdlIHRoZSBjb2x1bW4gbmFtZSBmb3JtIHRoZSAicm4iIGRlZmFjdWx0eSB0byAiRnVuZGluZ1JhbmsiCmNvbG5hbWVzKG5zZl9mdW5kaW5nX3NhdmUpWzFdIDwtICJGdW5kaW5nUmFuayIKIyBDaGFuZ2UgY2xhc3Mgb2YgbnNmX2Z1bmRpbmdfc2F2ZSRpZCB0byBjaGFyY2F0ZXIoZnJvbSBpbnRlZ2VyKQpuc2ZfZnVuZGluZ19zYXZlJGlkIDwtIGFzLmNoYXJhY3Rlcihuc2ZfZnVuZGluZ19zYXZlJGlkKQpsZGFfZ2FtbWEyIDwtIGRwbHlyOjpsZWZ0X2pvaW4obGRhX2dhbW1hLCBuc2ZfZnVuZGluZ19zYXZlLCAKICAgICAgICAgICAgICBzZWxlY3QoRnVuZGluZ1JhbmspLCBieSA9IGMoImRvY3VtZW50IiA9ICJpZCIpKSAKIyBjb252ZXJ0IEZ1bmRpbmdSYW5rIHRvIGEgbnVtYmVyCmxkYV9nYW1tYTIkRnVuZGluZ1JhbmsgPSBhcy5udW1lcmljKGxkYV9nYW1tYTIkRnVuZGluZ1JhbmspI1tsZGFfZ2FtbWEyXQojIGNvbnZlcnQgVG9waWMgdG8gYSBjaGFyYWN0ZXIKCiMgU29ydCB0aGUgdG9waWNzIGJ5IHRoZSBhbW9udCBvZiBtb25uZXkgdGhleSBicmF1Z2h0IGluLiBTdW0gb2YgZG9jdW1lbnQgR2FtbWEgPSAxCiMgVGhlIGZ1bmRpbmcgYXNzb2NpYXRlZCB3aXRoIGVhY2ggdG9waWMgaXMgdGhlIAojIHN1bSBvZiBhbGwgKGdhbW1hKmZ1bmRpbmcgYW1vdW50IGZvciBlYWNoIGdyYW50KQpsZGFfZ2FtbWEyIDwtIGxkYV9nYW1tYTIgJT4lIG11dGF0ZSh3b3J0aCA9IGdhbW1hICogYW1vdW50KQojIGNhbGN1bGF0ZSB0aGUgc3VtIG9mIHRoZSB3b3J0aCBmb3IgZWFjaCB0b3BpYwp0b3BpY193b3J0aCA8LSBhZ2dyZWdhdGUobGRhX2dhbW1hMiR3b3J0aCwgYnk9bGlzdCh0b3BpYyA9IGxkYV9nYW1tYTIkdG9waWMpLCBGVU4gPSAic3VtIikKI3RvcGljX3dvcnRoIDwtIGFnZ3JlZ2F0ZShsZGFfZ2FtbWEyJHdvcnRoLCBieT1saXN0KHRvcGljID0gYXMuY2hhcmFjdGVyKGxkYV9nYW1tYTIkdG9waWMpKSwgRlVOID0gInN1bSIpCmNvbG5hbWVzKHRvcGljX3dvcnRoKVsyXSA8LSAiV29ydGhTdW0iCiMgc29ydCBieSBtb3N0IHdvcnRoeSBvZiB0b3BpYwp0b3BpY193b3J0aCA8LSB0b3BpY193b3J0aCAlPiUgYXJyYW5nZShkZXNjKFdvcnRoU3VtKSkKIyBzYXZlIHRvcGljX3dvcnRoIGFzIENWUwoKc2V0RFQodG9waWNfd29ydGgsIGtlZXAucm93bmFtZXMgPSAiVG9waWNXb3J0aFJhbmsiKVtdCgpsZGFfZ2FtbWEzIDwtIGxlZnRfam9pbihsZGFfZ2FtbWEyLCB0b3BpY193b3J0aCwgCiAgICAgICAgICAgICAgc2VsZWN0KHgsIFRvcGljV29ydGhSYW5rKSwgYnkgPSAidG9waWMiKSAKbGRhX2dhbW1hMyRUb3BpY1dvcnRoUmFuayA9IGFzLm51bWVyaWMobGRhX2dhbW1hMyRUb3BpY1dvcnRoUmFuaykKCnAgPC0gZ2dwbG90KGxkYV9nYW1tYTMsIGFlcyh4PUZ1bmRpbmdSYW5rLCB5PVRvcGljV29ydGhSYW5rKSkgKwogICAgZ2VvbV9wb2ludChhZXMoc2l6ZT1nYW1tYSksIHNoYXBlID0gMTcpICsgICAjIGRyYXcgcG9pbnRzCiAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMobGltaXRzPWMoLjIsMSkpICsKICAgIHhsaW0oYygwLCBsZW5ndGgobnNmX2Z1bmRpbmdfc2F2ZSRpZCkpKSArIAogICB5bGltKGMoMSwgTnVtVG9waWNzKSkgKwogICBsYWJzKHN1YnRpdGxlPSJUb3BpYyBieSBEb2N1bWVudCIsIAogICAgICAgeT0iVG9waWMgIyIsIAogICAgICAgeD0iRG9jdW1lbnQgIyIsIAogICAgICAgdGl0bGU9IkdhbW1hIExldmVsIHNvcnRlZCBieSBGdW5kaW5nIExldmVsIiwgCiAgICAgICBjYXB0aW9uPSJUb3BpYyBBbmFseXNpcyIpCnAKZ2dzYXZlKHBhc3RlKHRvcGljRGlyLCIvRG9jVG9waWMucG5nIiwgc2VwPSIiKSwgaGVpZ2h0ID0gMTAsIGRldmljZSA9ICJwbmciKQojcG5nKHBhc3RlKHRvcGljRGlyLCIvVGVybXNMREFIaXN0b2dyYW0ucG5nIiwgc2VwPSIiKSwgd2lkdGggPSAxMjgwLCBoZWlnaHQgPSAxNjAwKQp3aGlsZSAoIWlzLm51bGwoZGV2Lmxpc3QoKSkpICBkZXYub2ZmKCkKcApgYGAKCmBgYHtyfQoKI0pvaW4gZnVuZGluZyBhbW91bnQgdG8gZGF0YQpuc2ZfZnVuZGluZ19qb2luIDwtIG5zZl9mdW5kaW5nX3NhdmUKbnNmX2Z1bmRpbmdfam9pbiRkb2N1bWVudCA8LSBhcy5jaGFyYWN0ZXIobnNmX2Z1bmRpbmdfam9pbiRpZCkKCmZ1bmRpbmdfZ2FtbWEgPC0gbWVyZ2UobGRhX2dhbW1hLCBuc2ZfZnVuZGluZ19qb2luLCBieT0iZG9jdW1lbnQiLCBhbGwueCA9IFRSVUUpCiAgZnVuZGluZ19nYW1tYSA8LSBzdWJzZXQoZnVuZGluZ19nYW1tYSwgc2VsZWN0PS1jKGlkLCBhYnN0cmFjdCkpCiAgCmZ1bmRpbmdfZ2FtbWEKCmBgYAoKCmBgYHtyfQojTmVlZCB0byBqb2luIGxkYV9nYW1tYSB3aXRoIGZ1bmRpbmcgYW1vdW50IHZlY3RvciwgdGhhdCB3b3VsZCBiZSBpbmZvcm1hdGl2ZQpkb2NfdG9wIDwtIGZ1bmRpbmdfZ2FtbWEgJT4lCiAgZ3JvdXBfYnkoZG9jdW1lbnQpICU+JQojICB0b3BfbigxLCBnYW1tYSkgJT4lCiAgdG9wX24oNSwgZ2FtbWEpICU+JQogIHVuZ3JvdXAoKSAlPiUKIyAgYXJyYW5nZShnYW1tYSkKICBhcnJhbmdlKGRvY3VtZW50LCBkZXNjKGdhbW1hKSkKd3JpdGUuY3N2KGRvY190b3AsIHBhc3RlKHRvcGljRGlyLCIvdG9wVG9waWNzNURvY3VtZW50cy5jc3YiLCBzZXA9IiIpKQoKZG9jX3RvcApgYGAKCmBgYHtyfQojQ29uc2Vuc3VzIGRvY3VtZW50IGZvciBlYWNoIHRvcGljCmRvY3VtZW50X3RvcGljcyA8LSBkb2NfdG9wICU+JQogIGNvdW50KGRvY3VtZW50LCB0b3BpYywgYW1vdW50KSAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgdG9wX24oMSwgbikgJT4lCiAgdW5ncm91cCgpICU+JQogIHRyYW5zbXV0ZShjb25zZW5zdXMgPSBkb2N1bWVudCwgdG9waWMsIGFtb3VudCkKCmRvY3VtZW50X3RvcGljcwpgYGAKCmBgYHtyfQojU2VlIHdoaWNoIGRvY3VtZW50cyBtaXNpZGVudGlmaWVkLCB0aGlzIGlzIHByb2JhYmx5IG5vdCB1c2VmdWwgZm9yIHVzLCBzaW5jZSBkb2N1bWVudHMgY2FuIGVhc2lseSBiZSBpbiBtdWx0aXBsZSB0b3BpY3MKZG9jX3RvcCAlPiUKICBpbm5lcl9qb2luKGRvY3VtZW50X3RvcGljcywgYnkgPSAidG9waWMiKSAlPiUKICBjb3VudChkb2N1bWVudCwgY29uc2Vuc3VzKQpgYGAKCgpgYGB7cn0KI0kgVEhJTksgVEhJUyBJUyBPUkRFUkVEIEJZIEZVTkRJTkcgQU1PVU5UIE5PVy4gV09VTEQgTElLRSBUTyBTRUUgV0hBVCBIQVBQRU5TIFdJVEggQSBCRVRURVIgQ09MT1IgU0NIRU1FIEFORCBUT1BJQyBDQVRFR09SSUVTIElOU1RFQUQgT0YgSU5ESVZJRFVBTCBUT1BJQ1MuCgojVmlzdWFsaXplIHdoaWNoIGRvY3VtZW50cyBoYXZlIHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IG9mIGJlaW5nIGdlbmVyYXRlZCBmcm9tIGVhY2ggdG9waWMKI1RvbyBiaWcKI01heWJlIGNhbiBhZGFwdCBmb3IgcHJvZ3JhbXMgaW5zdGVhZCBvZiBkb2N1bWVudHMKbGlicmFyeShmbGV4ZGFzaGJvYXJkKQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHN0bSkKbGlicmFyeShxdWFudGVkYSkKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHNjYWxlcykKCnAgPC0gZnVuZGluZ19nYW1tYSAlPiUgCiAgICBtdXRhdGUoZG9jdW1lbnQgPSBmYWN0b3IoZG9jdW1lbnQsIGxldmVscyA9IHJldih1bmlxdWUoZG9jdW1lbnQpKSkpICU+JQogICAgZ3JvdXBfYnkoZG9jdW1lbnQpICU+JQogICAgdG9wX24oMSkgJT4lCiAgICB1bmdyb3VwICU+JQogICAgZ2dwbG90KGFlcyhyZW9yZGVyKGRvY3VtZW50LCBhbW91bnQpLCB5PWdhbW1hLCBsYWJlbCA9IE5BLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSkpICsKICAgIGdlb21fY29sKCkgKwogICAgZ2VvbV90ZXh0KGFlcyhkb2N1bWVudCwgMC4wMSksIGhqdXN0ID0gMCwKICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAuNSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0Y0ODAyNCIsICIjMDA3N0NDIiwgIiM1RkJBN0QiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiM4QzYwQTciLCAiIzg5QzVEQSIsICIjREE1NzI0IiwgIiM3NEQ5NDQiLCAiI0NFNTBDQSIsICIjM0Y0OTIxIiwgIiNDMDcxN0MiLCAiI0NCRDU4OCIsICIjNUY3RkM3IiwgCiIjNjczNzcwIiwgIiNEM0Q5M0UiLCAiIzM4MzMzRSIsICIjNTA4NTc4IiwgIiNEN0MxQjEiLCAiIzY4OTAzMCIsICIjQUQ2RjNCIiwgIiNDRDlCQ0QiLCAKIiNEMTQyODUiLCAiIzZEREU4OCIsICIjNjUyOTI2IiwgIiM3RkRDQzAiLCAiI0M4NDI0OCIsICIjODU2OUQ1IiwgIiM1RTczOEYiLCAiI0QxQTMzRCIsIAoiIzhBN0M2NCIsICIjNTk5ODYxIiwgIiNBNDgwMjQiLCAiI0IwNzdDQyIsICIjQ0ZCQTdEIiwgIiNERkJBN0QiKSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBwZXJjZW50X2Zvcm1hdCgpKSArCiAgICBjb29yZF9mbGlwKCkgKyAKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZShheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCkpICsKICAgIGxhYnMoeCA9IE5VTEwsIHkgPSBleHByZXNzaW9uKGdhbW1hKSwgZmlsbCA9ICJUb3BpYyIpICAKcApnZ3NhdmUocGFzdGUodG9waWNEaXIsIi9Ub3BpY0dhbW1hTWFwLnBuZyIsIHNlcD0iIiksIGhlaWdodCA9IDEwLCBkZXZpY2UgPSAicG5nIikKI3BuZyhwYXN0ZSh0b3BpY0RpciwiL1Rlcm1zTERBSGlzdG9ncmFtLnBuZyIsIHNlcD0iIiksIHdpZHRoID0gMTI4MCwgaGVpZ2h0ID0gMTYwMCkKIyB3aGlsZSAoIWlzLm51bGwoZGV2Lmxpc3QoKSkpICBkZXYub2ZmKCkKcApgYGAKCgojIE5PIE5PVCBVU0UgVEhJUyBDSFVOSy4gVXNlIHRoZSBuZXh0IG9uZSB0byBtYWtlIGxkYV9qc29uIGZvciB1c2UgaW4gTERBdmlzIHBhY2thZ2UKI0luIHRoaXMgY2h1bmsgSSB3YXMgdHJ5aW5nIHRvIHVzZSBMREF2aXMgcGFja2FnZSwgYnV0IHdhcyB1bmFibGUgdG8gY29udmVydCB0b3BpY21vZGVscyBvdXRwdXQKCiNpbnN0YWxsLnBhY2thZ2VzKCJMREF2aXMiKQojZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJjcHNpZXZlcnQvTERBdmlzRGF0YSIpCgojJyBDb252ZXJ0IHRoZSBvdXRwdXQgb2YgYSB0b3BpY21vZGVscyBMYXRlbnQgRGlyaWNobGV0IEFsbG9jYXRpb24gdG8gSlNPTgojJyBmb3IgdXNlIHdpdGggTERBdmlzCiMnCiMnIEBwYXJhbSBmaXR0ZWQgT3V0cHV0IGZyb20gYSB0b3BpY21vZGVscyBcY29kZXtMREF9IG1vZGVsLiA9IGRlc2NfbGRhCiMnIEBwYXJhbSBjb3JwdXMgQ29ycHVzIG9iamVjdCB1c2VkIHRvIGNyZWF0ZSB0aGUgZG9jdW1lbnQgdGVybQojJyBtYXRyaXggZm9yIHRoZSBcY29kZXtMREF9IG1vZGVsLiBUaGlzIHNob3VsZCBoYXZlIGJlZW4gY3JlYXRlIHdpdGgKIycgdGhlIHRtIHBhY2thZ2UncyBcY29kZXtDb3JwdXN9IGZ1bmN0aW9uLiA9IG15Q29ycHVzCiMnIEBwYXJhbSBkb2NfdGVybSBUaGUgZG9jdW1lbnQgdGVybSBtYXRyaXggdXNlZCBpbiB0aGUgXGNvZGV7TERBfQojJyBtb2RlbC4gVGhpcyBzaG91bGQgaGF2ZSBiZWVuIGNyZWF0ZWQgd2l0aCB0aGUgdG0gcGFja2FnZSdzIAojJyBcY29kZXtEb2N1bWVudFRlcm1NYXRyaXh9IGZ1bmN0aW9uLiA9IGR0bQojJwojJyBAc2VlYWxzbyBcbGlua3tMREF2aXN9LgojJyBAZXhwb3J0CgoKdG9waWNtb2RlbHNfanNvbl9sZGF2aXMgPC0gZnVuY3Rpb24oZml0dGVkLCBjb3JwdXMsIGRvY190ZXJtKXsKICAgICMgUmVxdWlyZWQgcGFja2FnZXMKICAgIGxpYnJhcnkodG9waWNtb2RlbHMpCiAgICBsaWJyYXJ5KGRwbHlyKQogICAgbGlicmFyeShzdHJpbmdpKQogICAgbGlicmFyeSh0bSkKICAgIGxpYnJhcnkoTERBdmlzKQoKICAgICMgRmluZCByZXF1aXJlZCBxdWFudGl0aWVzCiAgIHBoaSA8LSBwb3N0ZXJpb3IoZml0dGVkKSR0ZXJtcyAlPiUgYXMubWF0cml4CiAgIHRoZXRhIDwtIHBvc3RlcmlvcihmaXR0ZWQpJHRvcGljcyAlPiUgYXMubWF0cml4CiAgIHZvY2FiIDwtIGNvbG5hbWVzKHBoaSkKICAgZG9jX2xlbmd0aCA8LSB2ZWN0b3IoKQogICBmb3IgKGkgaW4gMTpsZW5ndGgoY29ycHVzKSkgewogICAgICAgdGVtcCA8LSBwYXN0ZShjb3JwdXNbW2ldXSRjb250ZW50LCBjb2xsYXBzZSA9ICcgJykKICAgICAgIGRvY19sZW5ndGggPC0gYyhkb2NfbGVuZ3RoLCBzdHJpX2NvdW50KHRlbXAsIHJlZ2V4ID0gJ1xcUysnKSkKICB9CiAgCiAgdGVtcF9mcmVxdWVuY3kgPC0gYXMubWF0cml4KGRvY190ZXJtKQogIGZyZXFfbWF0cml4IDwtIGRhdGEuZnJhbWUoU1QgPSBjb2xuYW1lcyh0ZW1wX2ZyZXF1ZW5jeSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBGcmVxID0gY29sU3Vtcyh0ZW1wX2ZyZXF1ZW5jeSkpCiAgIHJtKHRlbXBfZnJlcXVlbmN5KQoKICAgICMgQ29udmVydCB0byBqc29uCiAgIAogICBqc29uX2xkYSA8LSBMREF2aXM6OmNyZWF0ZUpTT04ocGhpID0gcGhpLCB0aGV0YSA9IHRoZXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdm9jYWIgPSB2b2NhYiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRvYy5sZW5ndGggPSBkb2NfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVybS5mcmVxdWVuY3kgPSBmcmVxX21hdHJpeCRGcmVxKQoKICByZXR1cm4oanNvbl9sZGEpCn0KCmxkYV9qc29uIDwtIHRvcGljbW9kZWxzX2pzb25fbGRhdmlzKGZpdHRlZCA9IGRlc2NfbGRhLCBjb3JwdXMgPSBteUNvcnB1cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZG9jX3Rlcm0gPSBkdG0pCgoKYGBge3J9CiMgVGhpcyBjaGluayBkb2VzIGEgbXVsdGlkaW1lbnRpb25hbCBzY2FsaW5nIGludGVydG9waWMgZGlzdGFuY2UgbWFwLgogICAgIyBSZXF1aXJlZCBwYWNrYWdlcwogICAgbGlicmFyeSh0b3BpY21vZGVscykKICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KHN0cmluZ2kpCiAgICBsaWJyYXJ5KHRtKQogICAgbGlicmFyeShMREF2aXMpCiAgICBsaWJyYXJ5KGRldnRvb2xzKQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImNwc2lldmVydC9MREF2aXNEYXRhIikKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJjcHNpZXZlcnQvTERBdmlzIikKIycgVHJhbnNmb3JtIE1vZGVsIE91dHB1dCBmb3IgVXNlIHdpdGggdGhlIExEQXZpcyBQYWNrYWdlCiMnIGh0dHBzOi8vZ2lzdC5naXRodWIuY29tL3RyaW5rZXIvNDc3ZDdhZTY1ZmY2Y2E3M2NhY2UKIycgQ29udmVydCBhIFxwa2d7dG9waWNtb2RlbHN9IG91dHB1dCBpbnRvIHRoZSBKU09OIGZvcm0gcmVxdWlyZWQgYnkgdGhlIFxwa2d7TERBdmlzfSBwYWNrYWdlLgojJwojJyBAcGFyYW0gbW9kZWwgQSBcY29kZXtcbGlua1tde3RvcGljbW9kZWx9fSBvYmplY3QuCiMnIEBwYXJhbSBcbGRvdHMgQ3VycmVudGx5IGlnbm9yZWQuCiMnIEBzZWVhbHNvIFxjb2Rle1xsaW5rW0xEQXZpc117Y3JlYXRlSlNPTn19CiMnIEBleHBvcnQKIycgQGV4YW1wbGVzCiMnIFxkb250cnVuewojJyBkYXRhKCJBc3NvY2lhdGVkUHJlc3MiLCBwYWNrYWdlID0gInRvcGljbW9kZWxzIikKIycgbW9kZWwgPC0gTERBKEFzc29jaWF0ZWRQcmVzc1sxOjIwLF0sIGNvbnRyb2wgPSBsaXN0KGFscGhhID0gMC4xKSwgayA9IDMpCiMnIExEQXZpczo6c2VyVmlzKHRvcGljbW9kZWxzMkxEQXZpcyhtb2RlbCkpCiMnIH0KdG9waWNtb2RlbHMyTERBdmlzIDwtIGZ1bmN0aW9uKHgsIC4uLil7CiAgICBwb3N0IDwtIHRvcGljbW9kZWxzOjpwb3N0ZXJpb3IoeCkKICAgIGlmIChuY29sKHBvc3RbWyJ0b3BpY3MiXV0pIDwgMykgc3RvcCgiVGhlIG1vZGVsIG11c3QgY29udGFpbiA+IDIgdG9waWNzIikKICAgIG1hdCA8LSB4QHdvcmRhc3NpZ25tZW50cwogICAgTERBdmlzOjpjcmVhdGVKU09OKAogICAgICAgIHBoaSA9IHBvc3RbWyJ0ZXJtcyJdXSwgCiAgICAgICAgdGhldGEgPSBwb3N0W1sidG9waWNzIl1dLAogICAgICAgIHZvY2FiID0gY29sbmFtZXMocG9zdFtbInRlcm1zIl1dKSwKICAgICAgICBkb2MubGVuZ3RoID0gc2xhbTo6cm93X3N1bXMobWF0LCBuYS5ybSA9IFRSVUUpLAogICAgICAgIHRlcm0uZnJlcXVlbmN5ID0gc2xhbTo6Y29sX3N1bXMobWF0LCBuYS5ybSA9IFRSVUUpCiAgICApCn0KbGRhX2pzb24gPC0gdG9waWNtb2RlbHMyTERBdmlzKGRlc2NfbGRhKQpzZXJWaXMobGRhX2pzb24sIHJlb3JkZXIudG9waWMgPSBUUlVFKQpsaWJyYXJ5KFJKU09OSU8pCnggPC0gZnJvbUpTT04obGRhX2pzb24pCiAKYGBgCgpgYGB7cn0KIyMjIHRoaXMgd2lsbCBtYXAgdGhlIHRwaWMgbnVtYmVycyBmcm9tIHRoZSByZXN0IG9mIHRoZSBhbmFseXNpcyB0byB0aGUgbXVsdGlkaW1lbnRpb25hbCBzY2FsaW5nIHRvcGljIG51bWJlcnMuCnByaW50KCJPcmlnaW5hbCAtIE1EUyMiKQpmb3IoIGkgaW4gMTpOdW1Ub3BpY3MpIHsKICBwcmludChwYXN0ZShpLCB3aGljaCh4JHRvcGljLm9yZGVyID09IGkpKSkKfQpgYGAKCmBgYHtyfQoKIyBHZW5lcmF0ZSBiaWdyYW1zCgojIEJpZ3JhbSBzZXR1cApsaWJyYXJ5KHRpZHlyKQojbnNmX2Z1bmRpbmdfYmlncmFtICU+JQojICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKQojIHJlbW92ZSBiaWdyYW1zIHdpdGggc3RvcHdvcmRzCgpiaWdyYW1zX3NlcGFyYXRlZCA8LSBuc2ZfZnVuZGluZ19iaWdyYW0gJT4lCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpCgpiaWdyYW1zX2ZpbHRlcmVkIDwtIGJpZ3JhbXNfc2VwYXJhdGVkICU+JQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpI3JlcGxhY2UgYWxsIG9mIHRoZSBvcmlnaW5hbCB3b3JkcyBpbiB0aGUgbnNmX2Z1bmRpbmcgd2l0aCBsZW1tYXMKIyBUaGlzIGFsc28gbmVlZHMgdG8gYmUgZG9uZSBmb3IgdGhlIGJpZ3JhbXMgd2hlcmUgdGhlIHN1YnN0YXR1dGlvbiBoYXBwZW5zIGZvciBlYWNoIG9mIHRoZSB0d28gY29sdW1ucyBpbiB0aGUgYmlncmFtLiA/Pz8KbGVtbWFfYmlncmFtX25ldyA8LSBsZW1tYV91bmlxdWUKI3JlcGxhY2Ugd29yZCAxIHdpdGggbGVtbWEKbGVtbWFfYmlncmFtX25ldyA8LSBsZWZ0X2pvaW4oYmlncmFtc19maWx0ZXJlZCxsZW1tYV9iaWdyYW1fbmV3LCBieSA9IGMoIndvcmQxIiA9ICJ3b3JkIikpICU+JQogIG11dGF0ZSh3b3JkMSA9IGlmZWxzZShpcy5uYShsZW1tYSksIHdvcmRfY2xlYW4sIGxlbW1hKSkgIyAlPiUKIyBkcm9wIGxhc3QgdGhyZWUgY29sdW1ucyBpZCwgd29yZDEgYW5kIHdvcmQyCmxlbW1hX2JpZ3JhbV9uZXcgPC0gc3Vic2V0KGxlbW1hX2JpZ3JhbV9uZXcsIHNlbGVjdCA9IGMoaWQsIHdvcmQxLCB3b3JkMikpCmxlbW1hX2JpZ3JhbV9uZXcgPC0gbGVmdF9qb2luKGxlbW1hX2JpZ3JhbV9uZXcsIGxlbW1hX3VuaXF1ZSwgYnkgPSBjKCJ3b3JkMiIgPSAid29yZCIpKSAlPiUKICBtdXRhdGUod29yZDIgPSBpZmVsc2UoaXMubmEobGVtbWEpLCB3b3JkX2NsZWFuLCBsZW1tYSkpICMgJT4lCgpsZW1tYV9iaWdyYW1fbmV3IDwtIHN1YnNldChsZW1tYV9iaWdyYW1fbmV3LCBzZWxlY3QgPSBjKGlkLCB3b3JkMSwgd29yZDIpKQojbnNmX2Z1bmRpbmcgPC0gc3Vic2V0KCkKCiMgbmV3IGJpZ3JhbSBjb3VudHM6CmJpZ3JhbV9jb3VudHMgPC0gbGVtbWFfYmlncmFtX25ldyAlPiUgCiAgY291bnQod29yZDEsIHdvcmQyLCBzb3J0ID0gVFJVRSkKCmJpZ3JhbV9jb3VudHMKCmBgYAoKYWxhIGJlcm5oYXJkbGVhcm5zLmNvbQpUbyBzcGVlZCB0aGluZ3MgdXAsIHdlIHdpbGwgbm90IHdvcmsgb24gZXZlcnkgd29yZCBpbnN0YW5jZSBidXQgZmlsdGVyIG91dCBvbmx5IHRoZSB1bmlxdWUgb25lcy4gSWYgbmVlZGVkIHRoZSByZXN1bHRzIGZvciB0aGUgdW5pcXVlIHdvcmQgaW5zdGFuY2UgY2FuIGJlIGVhc2lseSBtYXBwZWQgYmFjayB0byB0aGUgbGFyZ2VyIGRhdGEtZnJhbWUuIFNvbWUgb2YgdGhlIHRlY2huaXF1ZXMgSSBhbSBnb2luZyB0byBwcmVzZW50IGFyZSBxdWl0ZSBjb21wdXRhdGlvbmFsIGV4cGFuc2l2ZSwgc28gaWYgeW91IGhhdmUgYSBtdWNoIGxhcmdlciBkYXRhIHNldCwgdGhlbiBtYXliZSB0aGV5IGFyZSBub3QgZmVhc2libGUuIEkgaGF2ZSBhZGRlZCBzeXN0ZW0udGltZSgpIHRvIHRoZSBwYXJ0aWN1bGFyIHdvcmstc3RlcHMgc28gdGhhdCB5b3UgY2FuIHNlZSBhbmQgZGVjaWRlIGZvciB5b3Vyc2VsZi4KCkZ1cnRoZXIsIHdlIGFwcGx5IHNvbWUgYmFzaWMgY2xlYW5pbmc6CgogICAgcmVtb3ZpbmcgdGhlIHBvc3Nlc3NpdmUgZW5kaW5nOiDigJlzCiAgICByZW1vdmluZyBhbGwgd29yZHMgY29udGFpbmluZyBub24gYWxwaGFiZXRpYyBjaGFyYWN0ZXJzIChkZXBlbmRpbmcgb24gdGhlIHRhc2sgYXQgaGFuZCB0aGlzIG1pZ2h0IGJlIGEgYmFkIGlkZWEgLSBlLmcuLCBpbiBzb2NpYWwgbWVkaWEgZW1vdGljb25zIGNhbiBiZSB2ZXJ5IGluZm9ybWF0aXZlKQogICAgcmVtb3ZpbmcgbWlzc2luZyB2YWx1ZXMKYGBge3J9CmJpZ3JhbXNfdW5pdGVkIDwtIGxlbW1hX2JpZ3JhbV9uZXcgJT4lCiAgdW5pdGUoYmlncmFtLCB3b3JkMSwgd29yZDIsIHNlcCA9ICIgIikKYmlncmFtc191bml0ZWQgJT4lCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkKIwpgYGAKCgpodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vbmdyYW1zLmh0bWwKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQpsaWJyYXJ5KGlncmFwaCkKCmJpZ3JhbV9ncmFwaCA8LSBiaWdyYW1fY291bnRzICU+JQogIGZpbHRlcihuID4gNikgJT4lCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkKCmxpYnJhcnkoZ2dyYXBoKQpzZXQuc2VlZCgyMDE3KQpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkKCiN0b3BpY0ZpbGVOYW1lIDwtIGdzdWIoIi5jc3YiLCJiaWdyYXBoLnBuZyIsIGZpbGVuYW1lKQojcG5nKHRvcGljRmlsZU5hbWUsIHdpZHRoID0gMTIsIGhlaWdodCA9IDgsIHVuaXRzID0gJ2luJywgcmVzID0gMzAwKQpwbmcocGFzdGUodG9waWNEaXIsIi9iaWdyYXBoLnBuZyIsIHNlcD0iIiksIHdpZHRoID0gMTIsIGhlaWdodCA9IDgsIHVuaXRzID0gJ2luJywgcmVzID0gMzAwKQpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkKZGV2Lm9mZigpCmBgYApURi1JREYgQW5hbHlzaXMgb2YgYmlncmFtcwpgYGB7cn0KYmlncmFtX3RmX2lkZiA8LSBiaWdyYW1zX3VuaXRlZCAlPiUKICBjb3VudChpZCwgYmlncmFtKSAlPiUKICBiaW5kX3RmX2lkZihiaWdyYW0sIGlkLCBuKSAlPiUKICBhcnJhbmdlKGRlc2ModGZfaWRmKSkKCmJpZ3JhbV90Zl9pZGYKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTUwLCBmaWcud2lkdGg9MTB9CiMgR3JhcGggdGhlIGJpZ3JhbXMuIFRISVMgV0lMTCBUQUtFIFNPTUUgVElNRQojIGNhdGVnb3JpZXMgYXJlIGNvbXByZXNzZWQKcCA8LSBiaWdyYW1fdGZfaWRmICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUKICBtdXRhdGUoYmlncmFtID0gZmFjdG9yKGJpZ3JhbSwgbGV2ZWxzID0gcmV2KHVuaXF1ZShiaWdyYW0pKSkpICU+JSAKICBncm91cF9ieShpZCkgJT4lIAogIHRvcF9uKDUpICU+JSAKICB1bmdyb3VwICU+JQogIGdncGxvdChhZXMoYmlncmFtLCB0Zl9pZGYsIGZpbGwgPSBpZCkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArCiAgZmFjZXRfd3JhcCh+aWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKI3RvcGljRmlsZU5hbWUgPC0gZ3N1YigiLmNzdiIsIkJpZ3JhbVRGSURGSGlzdG9ncmFtLnBuZyIsIGZpbGVuYW1lKQojZ2dzYXZlKHRvcGljRmlsZU5hbWUsIGhlaWdodCA9IDE0LCBkZXZpY2UgPSAicG5nIikKcApnZ3NhdmUocGFzdGUodG9waWNEaXIsIi9CaWdyYW1URklERkhpc3RvZ3JhbS5wbmciLCBzZXA9IiIpLCBoZWlnaHQgPSAzMCwgZGV2aWNlID0gInBuZyIpCiNwbmcocGFzdGUodG9waWNEaXIsIi9UZXJtc0xEQUhpc3RvZ3JhbS5wbmciLCBzZXA9IiIpLCB3aWR0aCA9IDEyODAsIGhlaWdodCA9IDE2MDApCndoaWxlICghaXMubnVsbChkZXYubGlzdCgpKSkgIGRldi5vZmYoKQoKYGBgCgpgYGB7cn0KI1RvcCBkb2N1bWVudHMgcGVyIHRvcGljCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3JlcGVsKQoKbnVtYmVyX29mX2RvY3VtZW50cyA9IDUgI251bWJlciBvZiB0b3AgZG9jcyB0byB2aWV3CnRpdGxlIDwtIHBhc3RlKCJMREEgVG9wIERvY3VtZW50cyBmb3IiLCBOdW1Ub3BpY3MsICJUb3BpY3MiKQoKdG9wX2RvY3VtZW50cyA8LSBsZGFfZ2FtbWEgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIGFycmFuZ2UodG9waWMsIGRlc2MoZ2FtbWEpKSAlPiUKICBzbGljZShzZXFfbGVuKG51bWJlcl9vZl9kb2N1bWVudHMpKSAlPiUKICBhcnJhbmdlKHRvcGljLCBnYW1tYSkgJT4lCiAgbXV0YXRlKHJvdyA9IHJvd19udW1iZXIoKSkgJT4lCiAgdW5ncm91cCgpICMlPiUKICAjcmUtbGFiZWwgdG9waWNzCiAgI211dGF0ZSh0b3BpYyA9IHBhc3RlKCJUb3BpYyIsIHRvcGljLCBzZXAgPSAiICIpKQoKI3RvcGljRmlsZU5hbWUgPC0gZ3N1YigiLmNzdiIsInRvcF9kb2N1bWVudHMuY3N2IiwgZmlsZW5hbWUpCndyaXRlLmNzdih0b3BfZG9jdW1lbnRzLCBwYXN0ZSh0b3BpY0RpciwiL3RvcF9kb2N1bWVudHMuY3N2Iiwgc2VwPSIiKSkKCnRvcF9iaWdyYW1zX2Zvcl9kb2NzIDwtIGJpZ3JhbV90Zl9pZGYgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIGFycmFuZ2UoaWQsIGRlc2ModGZfaWRmKSkgJT4lCiAgc2xpY2Uoc2VxX2xlbihudW1iZXJfb2ZfZG9jdW1lbnRzKSkgJT4lCiAgYXJyYW5nZShpZCwgdGZfaWRmKSAlPiUKICB1bmdyb3VwKCkgICU+JQogIHNlbGVjdChpZCwgYmlncmFtKSAKIAojIG1ha2UgdGhlIDUgYmlncmFtcyBpbnRvIDUgY29sdW1ucwp0b3BfYmlncmFtc19mb3JfZG9jc193aWRlIDwtIAogIGRjYXN0KHNldERUKHRvcF9iaWdyYW1zX2Zvcl9kb2NzKSwgaWR+cm93aWQoaWQsIHByZWZpeD0iYmlncmFtIiksIHZhbHVlLnZhcj0iYmlncmFtIikgICAKCiMgYWRkIHRoZSBiaWdyYW0gY29sdW1ucyB0byB0b3BfZG9jcwojIyMgZXJyb3IgLSByZXBlYXRzIG9mIGJpZ3JhbXMgZm9yIHRvbyBtYW55IGRvY3VtZW50cy4uLi4KIyBtYXliZSBub3QgYW4gZXJyb3IuLi4gZG9jdW1lbnRzIGluIHRoZSBzbWFlIHRvcGljIGFyZSBnZXR0aW5nIG1hbnkgb2YgdGhlIHNhbWUgYmlncmFtcy4KdG9wX2RvY3VtZW50cyA8LSBsZWZ0X2pvaW4odG9wX2RvY3VtZW50cywgdG9wX2JpZ3JhbXNfZm9yX2RvY3Nfd2lkZSwKICAgICAgICAgICAgc2VsZWN0KGJpZ3JhbTEsIGJpZ3JhbTIsIGJpZ3JhbTMsIGJpZ3JhbTQsIGJpZ3JhbTUpLAogICAgICAgICAgICBieSA9IGMoImRvY3VtZW50IiA9ICJpZCIpKQoKCmBgYAoKYGBge3J9CiNKb2luIGZ1bmRpbmcgYW1vdW50IGFuZCB0aXRsZSB0byB0b3BfZG9jc190aXRsZXMKCm5zZl9mdW5kaW5nX3RpdGxlcyA8LSBkYXRhX2ZyYW1lKGRvY3VtZW50ID0gTXlEYXRhJEF3YXJkTnVtYmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gTXlEYXRhJFRpdGxlLCBhbW91bnQgPSBNeURhdGEkQXdhcmRlZEFtb3VudFRvRGF0ZSkKIyBDb252ZXJ0IHRoZSBkb2N1bWVudCAjL2dyYW50IG51bWJlciB0byBhIGNoYXJjdGFlciBzdGluZyAgCm5zZl9mdW5kaW5nX3RpdGxlcyRkb2N1bWVudCA8LSBhcy5jaGFyYWN0ZXIobnNmX2Z1bmRpbmdfdGl0bGVzJGRvY3VtZW50KQoKdG9wX2RvY3VtZW50cyA8LSBmdWxsX2pvaW4odG9wX2RvY3VtZW50cywgbnNmX2Z1bmRpbmdfdGl0bGVzLCBieSA9ICJkb2N1bWVudCIpCiN0b3BfZG9jc190aXRsZXMgPC0gbWVyZ2UodG9wX2RvY3VtZW50cywgbnNmX2Z1bmRpbmdfdGl0bGVzLCBieT0iZG9jdW1lbnQiLCBhbGwueCA9IFRSVUUpCiN0b3BfZG9jc190aXRsZXMgPC0gc3Vic2V0KHRvcF9kb2NzX3RpdGxlcywgc2VsZWN0PS1jKHJvdykpCgojdG9wX2RvY3VtZW50cyA8LSBmdWxsX2pvaW4odG9wX2RvY3VtZW50cywgdG9wX2RvY3NfdGl0bGVzLCBieSA9ICJkb2N1bWVudCIpCiMgYWRkIHRvcGljIHdvcnRoIGFuZCByYW5rCnRvcF9kb2N1bWVudHMgPC0gZnVsbF9qb2luKHRvcF9kb2N1bWVudHMsIHRvcGljX3dvcnRoLCBieSA9ICJ0b3BpYyIpCgojdG9waWNGaWxlTmFtZSA8LSBnc3ViKCIuY3N2IiwidG9wX2RvY3VtZW50cy5jc3YiLCBmaWxlbmFtZSkKd3JpdGUuY3N2KHRvcF9kb2N1bWVudHMsIHBhc3RlKHRvcGljRGlyLCIvdG9wX2RvY3VtZW50cy5jc3YiLCBzZXA9IiIpKQoKI3RvcF9kb2NzX3RpdGxlcwoKYGBgCgoKYGBge3IgZmlnLmhlaWdodD0yMH0KI1RvcCBkb2N1bWVudHMgcGVyIHRvcGljCgoKI0ZJR1VSRSBJUyBCQUQgQU5EIE5FRURTIFdPUkssIE5PVCBSRUFMTFkgQSBVU0VGVUwgVklTVUFMSVpBVElPTiBBTllXQVkKdGl0bGUgPC0gcGFzdGUoIkxEQSBUb3AgRG9jdW1lbnRzIGZvciIsIE51bVRvcGljcywgIlRvcGljcyIpCgojZGVmaW5lIHNvbWUgY29sb3JzIHRvIHVzZSB0aHJvdWdob3V0Cm15X2NvbG9ycyA8LSBjKCIjRTY5RjAwIiwgIiM1NkI0RTkiLCAiIzAwOUU3MyIsICIjQ0M3OUE3IiwgIiNENTVFMDAiLCAiI0Q2NUUwMCIpCgojY3VzdG9taXplIGdncGxvdDIncyBkZWZhdWx0IHRoZW1lIHNldHRpbmdzCiN0aGlzIHR1dG9yaWFsIGRvZXNuJ3QgYWN0dWFsbHkgcGFzcyBhbnkgcGFyYW1ldGVycywgYnV0IHlvdSBtYXkgdXNlIGl0IGFnYWluIGluIGZ1dHVyZSB0dXRvcmlhbHMgc28gaXQncyBuaWNlIHRvIGhhdmUgdGhlIG9wdGlvbnMKdGhlbWVfbHlyaWNzIDwtIGZ1bmN0aW9uKGF0aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBnbWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgICBsdCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGxwID0gIm5vbmUiKQp7CiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksICNjZW50ZXIgdGhlIHRpdGxlCiAgICAgICAgYXhpcy50aWNrcyA9IGF0aWNrcywgI3NldCBheGlzIHRpY2tzIHRvIG9uIG9yIG9mZgogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBwZ21pbm9yLCAjdHVybiBvbiBvciBvZmYgdGhlIG1pbm9yIGdyaWQgbGluZXMKICAgICAgICBsZWdlbmQudGl0bGUgPSBsdCwgI3R1cm4gb24gb3Igb2ZmIHRoZSBsZWdlbmQgdGl0bGUKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBscCkgI3R1cm4gb24gb3Igb2ZmIHRoZSBsZWdlbmQKfQoKI2N1c3RvbWl6ZSB0aGUgdGV4dCB0YWJsZXMgZm9yIGNvbnNpc3RlbmN5IHVzaW5nIEhUTUwgZm9ybWF0dGluZwpteV9rYWJsZV9zdHlsaW5nIDwtIGZ1bmN0aW9uKGRhdCwgY2FwdGlvbikgewogIGthYmxlKGRhdCwgImh0bWwiLCBlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9IGNhcHRpb24pICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImNvbmRlbnNlZCIsICJib3JkZXJlZCIpLAogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQp9Cgp3b3JkX2NoYXJ0IDwtIGZ1bmN0aW9uKGRhdGEsIGlucHV0LCB0aXRsZSkgewogIGRhdGEgJT4lCiAgI3NldCB5ID0gMSB0byBqdXN0IHBsb3Qgb25lIHZhcmlhYmxlIGFuZCB1c2Ugd29yZCBhcyB0aGUgbGFiZWwKICBnZ3Bsb3QoYWVzKGFzLmZhY3Rvcihyb3cpLCAxLCBsYWJlbCA9IGlucHV0LCBmaWxsID0gZmFjdG9yKHRvcGljKSApKSArCiAgI3lvdSB3YW50IHRoZSB3b3Jkcywgbm90IHRoZSBwb2ludHMKICBnZW9tX3BvaW50KGNvbG9yID0gInRyYW5zcGFyZW50IikgKwogICNtYWtlIHN1cmUgdGhlIGxhYmVscyBkb24ndCBvdmVybGFwCiAgZ2VvbV9sYWJlbF9yZXBlbChudWRnZV94ID0gLjIsICAKICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9ICJ5IiwKICAgICAgICAgICAgICAgICAgIGJveC5wYWRkaW5nID0gMC4xLAogICAgICAgICAgICAgICAgICAgc2VnbWVudC5jb2xvciA9ICJ0cmFuc3BhcmVudCIsCiAgICAgICAgICAgICAgICAgICBzaXplID0gMykgKwogIGZhY2V0X2dyaWQofnRvcGljKSArCiAgdGhlbWVfbHlyaWNzKCkgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAjYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5KSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdCgibGlnaHRncmF5IiwgZmlsbCA9IE5BKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkpKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIHRpdGxlID0gdGl0bGUpICsKICAgICN4bGFiKE5VTEwpICsgeWxhYihOVUxMKSArCiAgI2dndGl0bGUodGl0bGUpICsKICBjb29yZF9mbGlwKCkKCn0KCmZhY2V0X3dyYXAofiB0b3BpYywgbmNvbCA9IDEwLCBzY2FsZXMgPSAiZnJlZSIpCgp3b3JkX2NoYXJ0KHRvcF9kb2N1bWVudHMsIHRvcF9kb2N1bWVudHMkZG9jdW1lbnQsIHRpdGxlKQoKCmBgYAoKYGBge3J9CiNEaXN0cmlidXRpb24gb2YgcHJvYmFiaWxpdGllcyBmb3IgYWxsIHRvcGljcwoKZ2dwbG90KGxkYV9nYW1tYSwgYWVzKGdhbW1hKSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgcHJvYmFiaWxpdGllcyBmb3IgYWxsIHRvcGljcyIsCiAgICAgICB5ID0gIk51bWJlciBvZiBkb2N1bWVudHMiLCB4ID0gZXhwcmVzc2lvbihnYW1tYSkpCmBgYAoKYGBge3J9CiNQcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gaW4gZWFjaCB0b3BpYwoKZ2dwbG90KGxkYV9nYW1tYSwgYWVzKGdhbW1hLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2hpc3RvZ3JhbShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBuY29sID0gNCkgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgcHJvYmFiaWxpdHkgZm9yIGVhY2ggdG9waWMiLAogICAgICAgeSA9ICJOdW1iZXIgb2YgZG9jdW1lbnRzIiwgeCA9IGV4cHJlc3Npb24oZ2FtbWEpKQojIEV2YWx1YXRlIG1vcmUgdG9waWNzIGFuZCBhbiBhdXRvbWF0aWMgbWV0aG9kIHRvIHNlZSB3aGVyZSBtZWFuIHNxIGdhbW1hIGJlZ2lucyB0byBkcm9wLgoKYGBgCmh0dHBzOi8va25vd2xlZGdlci5yYmluZC5pby9wb3N0L3RvcGljLW1vZGVsaW5nLXVzaW5nLXIvCldlIGNhbiBjb3JyZWxhdGUgdGhlIHRvcGljIGJ5IHRoZSBtZXRhZGF0YSBhdmFpbGFibGUsIGhlcmUgd2lsbCB3aWxsIHVzZSB0aGUgcHJlZGVmaW5lZCBjYXRlZ29yeSBvZiB0aGUgZG9jdW1lbnRzLiBJIGNvbnN0cnVjdCBhIGRhdGFmcmFtZSBiaW5kaW5nIHRoZSBEb2N1bWVudCBOdW1iZXIsIHRoZXRhIGZvciB0aGUgZG9jdW1lbnQsIGFuZCBpdHMgYXNzb2NpYXRlZCBjYXRlZ29yeS4gVGhlIGNvbHVtbiBtZWFucyBvZiB0aGV0YSwgZ3JvdXBlZCBieSBjYXRlZ29yeSBpcyBjYWxjdWxhdGVkLgpgYGB7cn0KdGhldGEgPC0gcG9zdGVyaW9yKGRlc2NfbGRhKSR0b3BpY3MgJT4lIGFzLm1hdHJpeAp4IDwtIGFzLmRhdGEuZnJhbWUocm93Lm5hbWVzKHRoZXRhKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpjb2xuYW1lcyh4KSA8LSBjKCJHcmFudE51bWJlciIpCngkTGVzc29uSWQgPC0gYXMubnVtZXJpYyh4JExlc3NvbklkKQp0aGV0YTIgPC0gY2JpbmQoeCwgdGhldGEpCnRoZXRhMiA8LSBkcGx5cjo6bGVmdF9qb2luKHRoZXRhMiwgRmlyc3RDYXRlZ29yeWJ5TGVzc29uLCBieSA9ICJHcmFudE51bWJlciIpCiMjIFJldHVybnMgY29sdW1uIG1lYW5zIGdyb3VwZWQgYnkgY2F0ZXJnb3J5CiMjIFRoZSBleGFtcGxlIHRoaXMgY2FtZSBmcm9tIGhhZCAyNyB0b3BpY3MuLi4gd2h5IDI4PyBXZSBoYXZlIHZhcmlhYmxlIE51bVRvcGljcwp0aGV0YS5tZWFuLmJ5IDwtIGJ5KHRoZXRhMlssIDI6MjhdLCB0aGV0YTIkQ2F0ZWdvcnksIGNvbE1lYW5zKQp0aGV0YS5tZWFuIDwtIGRvLmNhbGwoInJiaW5kIiwgdGhldGEubWVhbi5ieSkKI0kgY2FuIG5vdyBjb3JyZWxhdGUgdGhlIHRvcGljcy4KbGlicmFyeShjb3JycGxvdCkKYyA8LSBjb3IodGhldGEubWVhbikKY29ycnBsb3QoYywgbWV0aG9kID0gImNpcmNsZSIpCmBgYAoKRmluYW5jaWFsIGFuYWx5c2lzIG9uIEF3YXJkIEFtb3VudAoKYGBge3J9CiNKb2luIGFic3RyYWN0cyB0byBmdW5kaW5nIGFtb3VudApsaWJyYXJ5KGRwbHlyKQoKbnNmX2Ftb3VudCA8LSBkYXRhX2ZyYW1lKGlkID0gYXMuY2hhcmFjdGVyKE15RGF0YSRBd2FyZE51bWJlciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IE15RGF0YSRBd2FyZGVkQW1vdW50VG9EYXRlKQpuc2ZfYW1vdW50CgojQ29udGludWUgam9pbgoKbnNmX2Ftb3VudCA8LSBuc2ZfYW1vdW50ICU+JSAKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRpdGxlKSAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCgpkZXNjX3RmX2lkZiA8LSBmdWxsX2pvaW4oZGVzY190Zl9pZGYsIG5zZl9hbW91bnQsIGJ5ID0gImlkIikKZGVzY190Zl9pZGYKCiMgClRvcGljSW52ZXN0bWVudEJpZ3JhbSA8LSBmdWxsX2pvaW4odG9wX2RvY3VtZW50cywgdG9waWNfd29ydGgsIGJ5ID0gInRvcGljIikKd3JpdGUuY3N2KFRvcGljSW52ZXN0bWVudEJpZ3JhbSwgcGFzdGUodG9waWNEaXIsIi9Ub3BpY0ludmVzdG1lbnRCaWdyYW0uY3N2Iiwgc2VwPSIiKSkKCmBgYApJZGVudGlmeSB0aGUgdG9wIGVsZW1lbnQgY29kZXMgZm9yIGEgdG9waWMuCkVhY2ggZ3JhbnQgaGFzIGEgZ2FtbWEgYXNzb2NpYXRlZCB3aXRoIGVhY2ggdG9waWMuCkVhY2ggZ3JhbnQgaGFzIG9uZSBvciBtb3JlIGVsZW1lbnQgY29kZS4gClByb2dyYW0gY29kZXMgaW5kaWNhdGUgY3Jvc3MtTlNGIGluaXRpYXRpdmVzLiBUaGUgZWxlbWVudCBjb2RlIGlzIGhvbWUgYnV0IGZ1bmRpbmcgYWxzbyBhcnJpc2VzIGZyb20gdGhlIGNyb3NzIE5TRiBwcm9ncmFtcy4gRm9yIGEgZmlyc3QgYXByb3hpbWF0aW9uIGFuYWxheXNpcyBjYWxjdWxhdGUgdGhlIGVsZW1lbnQgY29udHJpYnV0aW9uIHRvIGEgdG9waWMgYnkgbXVsdGlwbHlpbmcgdGhlIGdyYW50IGFtb3VudCBmb3IgZWFjaCBncmFudCBpbiB0aGUgZm9yIGVhY2ggdG9waWMgUHJvZ3JhbUludmVzdG1lbnQgPSBTdW0gb2YgZ2FtbWEgKiBhbW91bnQgZGl2aWRlZCBldmVubHkgYW1vbmcgZWxtZW50IGNvZGVzLiBUaGlzIGRpc3RyaWJ1dGlvbiBpcyBhbiBhcHByb3hpbWF0aW9uIGJlY2F1c2UgdGhlIHNwbGl0IGZvciBmdW5kaW5nIG1heSBub3QgYmUgNTAvNTAuIGVnLiBpZiB0aGVyZSBpcyBvbmUgZWxlbWVudCBjb2RlIGFsbCBzcGVuZGluZyBpcyBhc3NpZ25lZCB0byB0aGF0IG9uZSBlbGVtZW50LiBJZiB0aGVyZSBhcmUgdHdvIHRoZSBpbnZlc3RlbWVudCB3aWxsIGJlIHNwbGl0IGJldHdlZW4gdGhlbS4gIE1vbmV5IG1heSBhY3R1YWxseSBiZSBjb21pbmcgZm9ybSBwcm9ncmFtIGNvZGVzIGFzIHdlbGwuLgpPdXRwdXQgRGF0YSBTdHJ1Y3R1cmU6IFRvcGljLCBFbGVtZW50IENvZGUsIFRvdGFsIEludmVzdG1lbnQKCg==